Index: /trunk/sources/HeuristicLab 3.3 Services.sln
===================================================================
--- /trunk/sources/HeuristicLab 3.3 Services.sln	(revision 12877)
+++ /trunk/sources/HeuristicLab 3.3 Services.sln	(revision 12878)
@@ -64,9 +64,12 @@
 	ProjectSection(SolutionItems) = preProject
 		.nuget\NuGet.Config = .nuget\NuGet.Config
-		.nuget\NuGet.exe = .nuget\NuGet.exe
 		.nuget\NuGet.targets = .nuget\NuGet.targets
 	EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.GeoIP", "HeuristicLab.ExtLibs\HeuristicLab.GeoIP\1.12\HeuristicLab.GeoIP.csproj", "{BE9B0229-9150-49A4-AEA6-BB58E055992F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Services.WebApp.Statistics-3.3", "HeuristicLab.Services.WebApp.Statistics\3.3\HeuristicLab.Services.WebApp.Statistics-3.3.csproj", "{1BC54353-7D4C-4734-BAA9-3D11B3242B59}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Services.WebApp.Maintenance-3.3", "HeuristicLab.Services.WebApp.Maintenance\3.3\HeuristicLab.Services.WebApp.Maintenance-3.3.csproj", "{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}"
 EndProject
 Global
@@ -243,4 +246,26 @@
 		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.ActiveCfg = Release|x86
 		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.Build.0 = Release|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x64.ActiveCfg = Debug|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x64.Build.0 = Debug|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x86.ActiveCfg = Debug|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x86.Build.0 = Debug|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x64.ActiveCfg = Release|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x64.Build.0 = Release|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x86.ActiveCfg = Release|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x86.Build.0 = Release|x86
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|x64.ActiveCfg = Debug|x64
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|x64.Build.0 = Debug|x64
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|x86.ActiveCfg = Debug|x86
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Debug|x86.Build.0 = Debug|x86
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Release|x64.ActiveCfg = Release|Any CPU
+		{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}.Release|x86.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Convert.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Convert.cs	(revision 12877)
+++ 	(revision )
@@ -1,363 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Data.Linq;
-using System.Linq;
-using DT = HeuristicLab.Services.Hive.Common.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public static class Convert {
-    #region Job
-    public static DT.Job ToDto(Job source) {
-      if (source == null) return null;
-      return new DT.Job {
-        Id = source.JobId,
-        CoresNeeded = source.CoresNeeded,
-        ExecutionTime = TimeSpan.FromMilliseconds(source.ExecutionTimeMs),
-        MemoryNeeded = source.MemoryNeeded,
-        ParentJobId = source.ParentJobId,
-        Priority = source.Priority,
-        PluginsNeededIds = (source.RequiredPlugins == null ? new List<Guid>() : source.RequiredPlugins.Select(x => x.PluginId).ToList()),
-        LastHeartbeat = source.LastHeartbeat,
-        State = source.State,
-        StateLog = (source.StateLogs == null ? new List<DT.StateLog>() : source.StateLogs.Select(x => Convert.ToDto(x)).OrderBy(x => x.DateTime).ToList()),
-        IsParentJob = source.IsParentJob,
-        FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished,
-        Command = source.Command,
-        LastJobDataUpdate = (source.JobData == null ? DateTime.MinValue : source.JobData.LastUpdate),
-        HiveExperimentId = source.HiveExperimentId,
-        IsPrivileged = source.IsPrivileged
-      };
-    }
-    public static Job ToEntity(DT.Job source) {
-      if (source == null) return null;
-      var entity = new Job(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Job source, Job target) {
-      if ((source != null) && (target != null)) {
-        target.JobId = source.Id;
-        target.CoresNeeded = source.CoresNeeded;
-        target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
-        target.MemoryNeeded = source.MemoryNeeded;
-        target.ParentJobId = source.ParentJobId;
-        target.Priority = source.Priority;
-        target.LastHeartbeat = source.LastHeartbeat;
-        target.State = source.State;
-        if (target.StateLogs == null) target.StateLogs = new EntitySet<StateLog>();
-        foreach (DT.StateLog sl in source.StateLog.Where(x => x.Id == Guid.Empty)) {
-          target.StateLogs.Add(Convert.ToEntity(sl));
-        }
-        target.IsParentJob = source.IsParentJob;
-        target.FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished;
-        target.Command = source.Command;
-        // RequiredPlugins are added by Dao
-        target.HiveExperimentId = source.HiveExperimentId;
-        target.IsPrivileged = source.IsPrivileged;
-      }
-    }
-    #endregion
-
-    #region JobData
-    public static DT.JobData ToDto(JobData source) {
-      if (source == null) return null;
-      return new DT.JobData { JobId = source.JobId, Data = source.Data.ToArray(), LastUpdate = source.LastUpdate };
-    }
-    public static JobData ToEntity(DT.JobData source) {
-      if (source == null) return null;
-      var entity = new JobData(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.JobData source, JobData target) {
-      if ((source != null) && (target != null)) {
-        target.JobId = source.JobId; target.Data = new Binary(source.Data); target.LastUpdate = source.LastUpdate;
-      }
-    }
-    #endregion
-
-    #region StateLog
-    public static DT.StateLog ToDto(StateLog source) {
-      if (source == null) return null;
-      return new DT.StateLog { Id = source.StateLogId, DateTime = source.DateTime, Exception = source.Exception, JobId = source.JobId, SlaveId = source.SlaveId, State = source.State, UserId = source.UserId };
-    }
-    public static StateLog ToEntity(DT.StateLog source) {
-      if (source == null) return null;
-      var entity = new StateLog(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.StateLog source, StateLog target) {
-      if ((source != null) && (target != null)) {
-        target.StateLogId = source.Id; target.DateTime = source.DateTime; target.Exception = source.Exception; target.JobId = source.JobId; target.SlaveId = source.SlaveId; target.State = source.State; target.UserId = source.UserId;
-      }
-    }
-    #endregion
-
-    #region Downtimes
-    public static DT.Downtime ToDto(Downtime source) {
-      if (source == null) return null;
-      return new DT.Downtime { Id = source.DowntimeId, AllDayEvent = source.AllDayEvent, EndDate = source.EndDate, Recurring = source.Recurring, RecurringId = source.RecurringId, ResourceId = source.ResourceId, StartDate = source.StartDate };
-    }
-    public static Downtime ToEntity(DT.Downtime source) {
-      if (source == null) return null;
-      var entity = new Downtime(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Downtime source, Downtime target) {
-      if ((source != null) && (target != null)) {
-        target.DowntimeId = source.Id; target.AllDayEvent = source.AllDayEvent; target.EndDate = source.EndDate; target.Recurring = source.Recurring; target.RecurringId = source.RecurringId; target.ResourceId = source.ResourceId; target.StartDate = source.StartDate;
-      }
-    }
-    #endregion
-
-    #region HiveExperiment
-    public static DT.HiveExperiment ToDto(HiveExperiment source) {
-      if (source == null) return null;
-      return new DT.HiveExperiment { Id = source.HiveExperimentId, Description = source.Description, Name = source.Name, OwnerUserId = source.OwnerUserId, DateCreated = source.DateCreated, ResourceNames = source.ResourceIds, LastAccessed = source.LastAccessed };
-    }
-    public static HiveExperiment ToEntity(DT.HiveExperiment source) {
-      if (source == null) return null;
-      var entity = new HiveExperiment(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.HiveExperiment source, HiveExperiment target) {
-      if ((source != null) && (target != null)) {
-        target.HiveExperimentId = source.Id; target.Description = source.Description; target.Name = source.Name; target.OwnerUserId = source.OwnerUserId; target.DateCreated = source.DateCreated; target.ResourceIds = source.ResourceNames; target.LastAccessed = source.LastAccessed;
-      }
-    }
-    #endregion
-
-    #region HiveExperimentPermission
-    public static DT.HiveExperimentPermission ToDto(HiveExperimentPermission source) {
-      if (source == null) return null;
-      return new DT.HiveExperimentPermission { HiveExperimentId = source.HiveExperimentId, GrantedUserId = source.GrantedUserId, GrantedByUserId = source.GrantedByUserId, Permission = source.Permission };
-    }
-    public static HiveExperimentPermission ToEntity(DT.HiveExperimentPermission source) {
-      if (source == null) return null;
-      var entity = new HiveExperimentPermission(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.HiveExperimentPermission source, HiveExperimentPermission target) {
-      if ((source != null) && (target != null)) {
-        target.HiveExperimentId = source.HiveExperimentId; target.GrantedUserId = source.GrantedUserId; target.GrantedByUserId = source.GrantedByUserId; target.Permission = source.Permission;
-      }
-    }
-    #endregion
-
-    #region Plugin
-    public static DT.Plugin ToDto(Plugin source) {
-      if (source == null) return null;
-      return new DT.Plugin { Id = source.PluginId, Name = source.Name, Version = new Version(source.Version), UserId = source.UserId, DateCreated = source.DateCreated, Hash = source.Hash };
-    }
-    public static Plugin ToEntity(DT.Plugin source) {
-      if (source == null) return null;
-      var entity = new Plugin(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Plugin source, Plugin target) {
-      if ((source != null) && (target != null)) {
-        target.PluginId = source.Id; target.Name = source.Name; target.Version = source.Version.ToString(); target.UserId = source.UserId; target.DateCreated = source.DateCreated; target.Hash = source.Hash;
-      }
-    }
-    #endregion
-
-    #region PluginData
-    public static DT.PluginData ToDto(PluginData source) {
-      if (source == null) return null;
-      return new DT.PluginData { Id = source.PluginDataId, PluginId = source.PluginId, Data = source.Data.ToArray(), FileName = source.FileName };
-    }
-    public static PluginData ToEntity(DT.PluginData source) {
-      if (source == null) return null;
-      var entity = new PluginData(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.PluginData source, PluginData target) {
-      if ((source != null) && (target != null)) {
-        target.PluginDataId = source.Id; target.PluginId = source.PluginId; target.Data = new Binary(source.Data); target.FileName = source.FileName;
-      }
-    }
-    #endregion
-
-    #region Slave
-    public static DT.Slave ToDto(Slave source) {
-      if (source == null) return null;
-      return new DT.Slave {
-        Id = source.ResourceId,
-        ParentResourceId = source.ParentResourceId,
-        Cores = source.Cores,
-        CpuSpeed = source.CpuSpeed,
-        FreeCores = source.FreeCores,
-        FreeMemory = source.FreeMemory,
-        IsAllowedToCalculate = source.IsAllowedToCalculate,
-        Memory = source.Memory,
-        Name = source.Name,
-        SlaveState = source.SlaveState,
-        CpuArchitecture = source.CpuArchitecture,
-        OperatingSystem = source.OperatingSystem,
-        LastHeartbeat = source.LastHeartbeat,
-        CpuUtilization = source.CpuUtilization
-      };
-    }
-    public static Slave ToEntity(DT.Slave source) {
-      if (source == null) return null;
-      var entity = new Slave(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Slave source, Slave target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id;
-        target.ParentResourceId = source.ParentResourceId;
-        target.Cores = source.Cores;
-        target.CpuSpeed = source.CpuSpeed;
-        target.FreeCores = source.FreeCores;
-        target.FreeMemory = source.FreeMemory;
-        target.IsAllowedToCalculate = source.IsAllowedToCalculate;
-        target.Memory = source.Memory;
-        target.Name = source.Name;
-        target.SlaveState = source.SlaveState;
-        target.CpuArchitecture = source.CpuArchitecture;
-        target.OperatingSystem = source.OperatingSystem;
-        target.LastHeartbeat = source.LastHeartbeat;
-        target.CpuUtilization = source.CpuUtilization;
-      }
-    }
-    #endregion
-
-    #region SlaveGroup
-    public static DT.SlaveGroup ToDto(SlaveGroup source) {
-      if (source == null) return null;
-      return new DT.SlaveGroup { Id = source.ResourceId, Name = source.Name, ParentResourceId = source.ParentResourceId };
-    }
-    public static SlaveGroup ToEntity(DT.SlaveGroup source) {
-      if (source == null) return null;
-      var entity = new SlaveGroup(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.SlaveGroup source, SlaveGroup target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id; target.Name = source.Name; target.ParentResourceId = source.ParentResourceId;
-      }
-    }
-    #endregion
-
-    #region Resource
-    public static DT.Resource ToDto(Resource source) {
-      if (source == null) return null;
-      return new DT.Resource { Id = source.ResourceId, Name = source.Name, ParentResourceId = source.ParentResourceId };
-    }
-    public static Resource ToEntity(DT.Resource source) {
-      if (source == null) return null;
-      var entity = new Resource(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Resource source, Resource target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id; target.Name = source.Name; target.ParentResourceId = source.ParentResourceId;
-      }
-    }
-    #endregion
-
-    #region Statistics
-    public static DT.Statistics ToDto(Statistics source) {
-      if (source == null) return null;
-      return new DT.Statistics {
-        Id = source.StatisticsId,
-        TimeStamp = source.Timestamp,
-        SlaveStatistics = source.SlaveStatistics.Select(x => Convert.ToDto(x)).ToArray(),
-        UserStatistics = source.UserStatistics.Select(x => Convert.ToDto(x)).ToArray()
-      };
-    }
-    public static Statistics ToEntity(DT.Statistics source) {
-      if (source == null) return null;
-      var entity = new Statistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Statistics source, Statistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.Timestamp = source.TimeStamp;
-
-      }
-    }
-    #endregion
-
-    #region SlaveStatistics
-    public static DT.SlaveStatistics ToDto(SlaveStatistics source) {
-      if (source == null) return null;
-      return new DT.SlaveStatistics {
-        Id = source.StatisticsId,
-        SlaveId = source.SlaveId,
-        Cores = source.Cores,
-        CpuUtilization = source.CpuUtilization,
-        FreeCores = source.FreeCores,
-        FreeMemory = source.FreeMemory,
-        Memory = source.Memory
-      };
-    }
-    public static SlaveStatistics ToEntity(DT.SlaveStatistics source) {
-      if (source == null) return null;
-      var entity = new SlaveStatistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.SlaveStatistics source, SlaveStatistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.SlaveId = source.SlaveId;
-        target.Cores = source.Cores;
-        target.CpuUtilization = source.CpuUtilization;
-        target.FreeCores = source.FreeCores;
-        target.FreeMemory = source.FreeMemory;
-        target.Memory = source.Memory;
-      }
-    }
-    #endregion
-
-    #region UserStatistics
-    public static DT.UserStatistics ToDto(UserStatistics source) {
-      if (source == null) return null;
-      return new DT.UserStatistics {
-        Id = source.StatisticsId,
-        UserId = source.UserId,
-        UsedCores = source.UsedCores,
-        ExecutionTime = TimeSpan.FromMilliseconds(source.ExecutionTimeMs),
-        ExecutionTimeFinishedJobs = TimeSpan.FromMilliseconds(source.ExecutionTimeMsFinishedJobs),
-        StartToEndTime = TimeSpan.FromMilliseconds(source.StartToEndTimeMs)
-      };
-    }
-    public static UserStatistics ToEntity(DT.UserStatistics source) {
-      if (source == null) return null;
-      var entity = new UserStatistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.UserStatistics source, UserStatistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.UserId = source.UserId;
-        target.UsedCores = source.UsedCores;
-        target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
-        target.ExecutionTimeMsFinishedJobs = source.ExecutionTimeFinishedJobs.TotalMilliseconds;
-        target.StartToEndTimeMs = source.StartToEndTime.TotalMilliseconds;
-      }
-    }
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/AssignedResourceDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/AssignedResourceDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/AssignedResourceDao.cs	(revision 12878)
@@ -0,0 +1,58 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class AssignedResourceDao : GenericDao<Guid, AssignedResource> {
+    public AssignedResourceDao(DataContext dataContext) : base(dataContext) { }
+
+    public override AssignedResource GetById(Guid id) {
+      throw new NotImplementedException();
+    }
+
+    public IQueryable<AssignedResource> GetByTaskId(Guid taskId) {
+      return Table.Where(x => x.TaskId == taskId);
+    }
+
+    public bool TaskIsAllowedToBeCalculatedBySlave(Guid taskId, Guid slaveId) {
+      return DataContext.ExecuteQuery<int>(TaskIsAllowedToBeCalculatedBySlaveQueryString, slaveId, taskId).First() > 0;
+    }
+
+    #region String queries
+    private const string TaskIsAllowedToBeCalculatedBySlaveQueryString = @"
+      WITH pr AS (
+        SELECT ResourceId, ParentResourceId
+        FROM [Resource]
+        WHERE ResourceId = {0}
+        UNION ALL
+        SELECT r.ResourceId, r.ParentResourceId
+        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
+      )
+      SELECT COUNT(ar.TaskId)
+      FROM pr JOIN AssignedResources ar ON pr.ResourceId = ar.ResourceId
+      WHERE ar.TaskId = {1}
+    ";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/DowntimeDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/DowntimeDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/DowntimeDao.cs	(revision 12878)
@@ -0,0 +1,46 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class DowntimeDao : GenericDao<Guid, Downtime> {
+    public DowntimeDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Downtime GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IQueryable<Downtime> GetByResourceId(Guid id) {
+      return Table.Where(x => x.ResourceId == id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Downtime> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid downtimeId) =>
+        (from downtime in db.GetTable<Downtime>()
+         where downtime.DowntimeId == downtimeId
+         select downtime).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/GenericDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/GenericDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/GenericDao.cs	(revision 12878)
@@ -0,0 +1,125 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+using System.Linq.Expressions;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public abstract class GenericDao<PK, T> : IGenericDao<PK, T> where T : class {
+    private readonly DataContext dataContext;
+    protected DataContext DataContext {
+      get { return dataContext; }
+    }
+
+    protected Table<T> Table {
+      get { return dataContext.GetTable<T>(); }
+    }
+
+    protected GenericDao(DataContext dataContext) {
+      this.dataContext = dataContext;
+    }
+
+    #region IGenericDao<PK,T> Members
+    public abstract T GetById(PK id);
+
+    public IQueryable<T> Get(Expression<Func<T, bool>> predicate) {
+      return Table.Where(predicate);
+    }
+
+    public IQueryable<T> GetAll() {
+      return Table;
+    }
+
+    public T Save(T entity) {
+      Table.InsertOnSubmit(entity);
+      return entity;
+    }
+
+    public IEnumerable<T> Save(params T[] entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public IEnumerable<T> Save(IEnumerable<T> entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public T SaveOrAttach(T entity) {
+      if (Table.Contains(entity)) {
+        if (Table.GetOriginalEntityState(entity) == null) {
+          Table.Attach(entity);
+        }
+      } else {
+        Table.InsertOnSubmit(entity);
+      }
+      return entity;
+    }
+
+    public IEnumerable<T> SaveOrAttach(params T[] entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public IEnumerable<T> SaveOrAttach(IEnumerable<T> entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public void Delete(PK id) {
+      T entity = GetById(id);
+      if (entity != null) {
+        Table.DeleteOnSubmit(entity);
+      }
+    }
+
+    public virtual void Delete(IEnumerable<PK> ids) {
+      foreach (var id in ids) {
+        Delete(id);
+      }
+    }
+
+    public void Delete(T entity) {
+      Table.DeleteOnSubmit(entity);
+    }
+
+    public void Delete(IEnumerable<T> entities) {
+      foreach (var entity in entities) {
+        Delete(entity);
+      }
+    }
+
+    public bool Exists(T entity) {
+      return ExistsQuery(DataContext, entity);
+    }
+
+    public bool Exists(PK id) {
+      return GetById(id) != null;
+    }
+    #endregion
+
+    #region Compiled queries
+    private static readonly Func<DataContext, T, bool> ExistsQuery =
+      CompiledQuery.Compile((DataContext db, T entity) =>
+        db.GetTable<T>().Contains(entity));
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs	(revision 12878)
@@ -0,0 +1,67 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+  public class DimClientDao : GenericDao<Guid, DimClient> {
+    public DimClientDao(DataContext dataContext) : base(dataContext) { }
+
+    public override DimClient GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IQueryable<DimClient> GetActiveClients() {
+      return Table.Where(x => x.ExpirationTime == null);
+    }
+
+    public IQueryable<DimClient> GetExpiredClients() {
+      return Table.Where(x => x.ExpirationTime != null);
+    }
+
+    public int UpdateExpirationTime(IEnumerable<Guid> ids, DateTime time) {
+      string paramIds = string.Join(",", ids.Select(x => string.Format("'{0}'", x)));
+      if (!string.IsNullOrWhiteSpace(paramIds)) {
+        string query = string.Format(UpdateExpirationTimeQuery, "{0}", paramIds);
+        return DataContext.ExecuteCommand(query, time);
+      }
+      return 0;
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, DimClient> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid id) =>
+        (from dimClient in db.GetTable<DimClient>()
+         where dimClient.Id == id
+         select dimClient).SingleOrDefault());
+    #endregion
+
+    #region String queries
+    private const string UpdateExpirationTimeQuery =
+      @"UPDATE [statistics].[DimClient] 
+           SET ExpirationTime = {0} 
+         WHERE Id IN ({1});";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 12878)
@@ -0,0 +1,54 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+  public class DimJobDao : GenericDao<Guid, DimJob> {
+    public DimJobDao(DataContext dataContext) : base(dataContext) { }
+
+    public override DimJob GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IQueryable<DimJob> GetByUserId(Guid id) {
+      return Table.Where(x => x.UserId == id);
+    }
+
+    public IQueryable<DimJob> GetNotCompletedJobs() {
+      return Table.Where(x => x.DateCompleted == null);
+    }
+
+    public IQueryable<DimJob> GetCompletedJobs() {
+      return Table.Where(x => x.DateCompleted != null);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, DimJob> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid id) =>
+        (from dimJob in db.GetTable<DimJob>()
+         where dimJob.JobId == id
+         select dimJob).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimTimeDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimTimeDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimTimeDao.cs	(revision 12878)
@@ -0,0 +1,64 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+  public class DimTimeDao : GenericDao<DateTime, DimTime> {
+    public DimTimeDao(DataContext dataContext) : base(dataContext) { }
+
+    public override DimTime GetById(DateTime id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public DimTime GetLastEntry() {
+      return GetLastEntryQuery(DataContext);
+    }
+
+    public int DeleteUnusedTimes() {
+      return DataContext.ExecuteCommand(DeleteUnusedTimeEntriesStringQuery);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, DateTime, DimTime> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, DateTime id) =>
+        (from dimTime in db.GetTable<DimTime>()
+         where dimTime.Time == id
+         select dimTime).SingleOrDefault());
+
+    private static readonly Func<DataContext, DimTime> GetLastEntryQuery =
+      CompiledQuery.Compile((DataContext db) =>
+        (from dimTime in db.GetTable<DimTime>()
+         orderby dimTime.Time descending
+         select dimTime).FirstOrDefault());
+    #endregion
+
+    #region String queries
+    private const string DeleteUnusedTimeEntriesStringQuery = @"
+      DELETE FROM [statistics].[DimTime]
+      WHERE NOT EXISTS (SELECT [Time] FROM [statistics].[FactClientInfo] fci
+                        WHERE fci.[Time] = [statistics].[DimTime].[Time]);    
+    ";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimUserDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimUserDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimUserDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+  public class DimUserDao : GenericDao<Guid, DimUser> {
+    public DimUserDao(DataContext dataContext) : base(dataContext) { }
+
+    public override DimUser GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, DimUser> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid id) =>
+        (from dimUser in db.GetTable<DimUser>()
+         where dimUser.UserId == id
+         select dimUser).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs	(revision 12878)
@@ -0,0 +1,67 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+
+  public class LastUpdateTimestamp {
+    public Guid ResourceId { get; set; }
+    public DateTime? Timestamp { get; set; }
+  }
+
+  public class FactClientInfoDao : GenericDao<Guid, FactClientInfo> {
+    private Table<DimClient> DimClientTable {
+      get { return DataContext.GetTable<DimClient>(); }
+    }
+
+    private Table<DimUser> DimUserTable {
+      get { return DataContext.GetTable<DimUser>(); }
+    }
+
+    private Table<DimTime> DimTimeTable {
+      get { return DataContext.GetTable<DimTime>(); }
+    }
+
+    public FactClientInfoDao(DataContext dataContext) : base(dataContext) { }
+
+    public override FactClientInfo GetById(Guid id) {
+      throw new NotImplementedException();
+    }
+
+    public IQueryable<LastUpdateTimestamp> GetLastUpdateTimestamps() {
+      return from cf in Table
+             join r in DimClientTable on cf.ClientId equals r.Id
+             group cf by r.ResourceId
+               into grpFacts
+               select new LastUpdateTimestamp {
+                 ResourceId = grpFacts.Key,
+                 Timestamp = grpFacts.Max(x => x.Time),
+               };
+    }
+
+    public IQueryable<FactClientInfo> GetByClientId(Guid id) {
+      return Table.Where(x => x.ClientId == id);
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs	(revision 12878)
@@ -0,0 +1,120 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics {
+  public class FactTaskDao : GenericDao<Guid, FactTask> {
+
+    private static readonly TaskState[] CompletedStates = { TaskState.Finished, TaskState.Aborted, TaskState.Failed };
+
+    private Table<DimClient> DimClientTable {
+      get { return DataContext.GetTable<DimClient>(); }
+    }
+    private Table<DimJob> DimJobTable {
+      get { return DataContext.GetTable<DimJob>(); }
+    }
+
+    public FactTaskDao(DataContext dataContext) : base(dataContext) { }
+
+    public override FactTask GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IQueryable<FactTask> GetNotFinishedTasks() {
+      return Table.Where(x => x.EndTime == null);
+    }
+
+    public IQueryable<FactTask> GetByJobId(Guid id) {
+      return Table.Where(x => x.JobId == id);
+    }
+
+    public DateTime? GetLastCompletedTaskFromJob(Guid id) {
+      return GetLastCompletedTaskFromJobQuery(DataContext, id);
+    }
+
+    public IQueryable<FactTask> GetByClientId(Guid id) {
+      return Table.Where(x => x.LastClientId == id);
+    }
+
+    public IQueryable<FactTask> GetTasksWithException() {
+      return Table.Where(x => x.Exception.Trim() != string.Empty);
+    }
+
+    public IQueryable<FactTask> GetByGroupId(Guid id) {
+      return from factTask in Table
+             join client in DimClientTable on factTask.LastClientId equals client.Id
+             where client.ResourceGroupId == id
+             select factTask;
+    }
+
+    public IQueryable<FactTask> GetByUserId(Guid id) {
+      return from factTask in Table
+             join job in DimJobTable on factTask.JobId equals job.JobId
+             where job.UserId == id
+             select factTask;
+    }
+
+    public IQueryable<FactTask> GetCompletedTasks() {
+      return Table.Where(x => CompletedStates.Contains(x.TaskState));
+    }
+
+    public override void Delete(IEnumerable<Guid> ids) {
+      string paramIds = string.Join(",", ids.Select(x => string.Format("'{0}'", x)));
+      if (!string.IsNullOrWhiteSpace(paramIds)) {
+        string query = string.Format(BatchDeleteQuery, paramIds);
+        DataContext.ExecuteCommand(query);
+      }
+    }
+
+    public void DeleteByJobId(Guid jobId) {
+      DataContext.ExecuteCommand(DeleteByJobIdQuery, jobId);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, FactTask> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid id) =>
+        (from factTask in db.GetTable<FactTask>()
+         where factTask.TaskId == id
+         select factTask).SingleOrDefault());
+
+
+    private static readonly Func<DataContext, Guid, DateTime?> GetLastCompletedTaskFromJobQuery =
+      CompiledQuery.Compile((DataContext db, Guid id) =>
+        (from factTask in db.GetTable<FactTask>()
+         where factTask.JobId == id && factTask.EndTime != null
+         select factTask.EndTime).Max());
+    #endregion
+
+    #region String queries
+    private const string BatchDeleteQuery =
+      @"DELETE FROM [statistics].[FactTask]
+         WHERE TaskId IN ({0});";
+
+    private const string DeleteByJobIdQuery =
+      @"DELETE FROM [statistics].[FactTask]
+         WHERE JobId = {0};";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class JobDao : GenericDao<Guid, Job> {
+    public JobDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Job GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Job> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid jobId) =>
+        (from job in db.GetTable<Job>()
+         where job.JobId == jobId
+         select job).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobPermissionDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobPermissionDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobPermissionDao.cs	(revision 12878)
@@ -0,0 +1,71 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class JobPermissionDao : GenericDao<Guid, JobPermission> {
+    public JobPermissionDao(DataContext dataContext) : base(dataContext) { }
+
+    public override JobPermission GetById(Guid id) {
+      throw new NotImplementedException();
+    }
+
+    public JobPermission GetByJobAndUserId(Guid jobId, Guid userId) {
+      return GetByJobAndUserIdQuery(DataContext, jobId, userId);
+    }
+
+    public IQueryable<JobPermission> GetByJobId(Guid jobId) {
+      return Table.Where(x => x.JobId == jobId);
+    }
+
+    public void SetJobPermission(Guid jobId, Guid grantedByUserId, Guid grantedUserId, Permission permission) {
+      var jobPermission = GetByJobAndUserId(jobId, grantedUserId);
+      if (jobPermission != null) {
+        if (permission == Permission.NotAllowed) {
+          Delete(jobPermission);
+        } else {
+          jobPermission.Permission = permission;
+          jobPermission.GrantedByUserId = grantedByUserId;
+        }
+      } else {
+        if (permission != Permission.NotAllowed) {
+          Save(new JobPermission {
+            JobId = jobId,
+            GrantedByUserId = grantedByUserId,
+            GrantedUserId = grantedUserId,
+            Permission = permission
+          });
+        }
+      }
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Guid, JobPermission> GetByJobAndUserIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid jobId, Guid userId) =>
+        (from jobPermission in db.GetTable<JobPermission>()
+         where jobPermission.JobId == jobId && jobPermission.GrantedUserId == userId
+         select jobPermission).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/LifecycleDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/LifecycleDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/LifecycleDao.cs	(revision 12878)
@@ -0,0 +1,63 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class LifecycleDao : GenericDao<int, Lifecycle> {
+    public LifecycleDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Lifecycle GetById(int id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public Lifecycle GetLastLifecycle() {
+      return GetLastLifecycleQuery(DataContext);
+    }
+
+    public void UpdateLifecycle() {
+      var entity = GetLastLifecycle();
+      if (entity != null) {
+        entity.LastCleanup = DateTime.Now;
+      } else {
+        Save(new Lifecycle {
+          LifecycleId = 0,
+          LastCleanup = DateTime.Now
+        });
+      }
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, int, Lifecycle> GetByIdQuery =
+     CompiledQuery.Compile((DataContext db, int lifecycleId) =>
+       (from lifecycle in db.GetTable<Lifecycle>()
+        where lifecycle.LifecycleId == lifecycleId
+        select lifecycle).SingleOrDefault());
+
+    private static readonly Func<DataContext, Lifecycle> GetLastLifecycleQuery =
+      CompiledQuery.Compile((DataContext db) =>
+        (from lifecycle in db.GetTable<Lifecycle>()
+         select lifecycle).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDao.cs	(revision 12878)
@@ -0,0 +1,70 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class PluginDao : GenericDao<Guid, Plugin> {
+    public PluginDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Plugin GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IEnumerable<Plugin> GetByHash(byte[] hash) {
+      return GetByHashQuery(DataContext).Where(x => x.Hash.SequenceEqual(hash));
+    }
+
+    public int DeleteUnusedPlugins() {
+      return DataContext.ExecuteCommand(DeleteUnusedPluginsStringQuery);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Plugin> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid pluginId) =>
+        (from plugin in db.GetTable<Plugin>()
+         where plugin.PluginId == pluginId
+         select plugin).SingleOrDefault());
+
+    private static readonly Func<DataContext, IEnumerable<Plugin>> GetByHashQuery =
+      CompiledQuery.Compile((DataContext db) =>
+        (from plugin in db.GetTable<Plugin>()
+         where plugin.Hash != null
+         select plugin));
+    #endregion
+
+    #region String queries
+    private const string DeleteUnusedPluginsStringQuery = @"
+      DELETE FROM [Plugin]
+      WHERE  [Plugin].[PluginId] NOT IN (
+        SELECT DISTINCT rp.[PluginId] 
+        FROM [RequiredPlugins] rp
+        WHERE EXISTS (SELECT [TaskId] 
+                      FROM [Task] t
+                      WHERE t.[TaskId] = rp.[TaskId])
+      );
+    ";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDataDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDataDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/PluginDataDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class PluginDataDao : GenericDao<Guid, PluginData> {
+    public PluginDataDao(DataContext dataContext) : base(dataContext) { }
+
+    public override PluginData GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, PluginData> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid pluginDataId) =>
+        (from pluginData in db.GetTable<PluginData>()
+         where pluginData.PluginDataId == pluginDataId
+         select pluginData).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/RequiredPluginDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/RequiredPluginDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/RequiredPluginDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class RequiredPluginDao : GenericDao<Guid, RequiredPlugin> {
+    public RequiredPluginDao(DataContext dataContext) : base(dataContext) { }
+
+    public override RequiredPlugin GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, RequiredPlugin> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid requiredPluginId) =>
+        (from requiredPlugin in db.GetTable<RequiredPlugin>()
+         where requiredPlugin.RequiredPluginId == requiredPluginId
+         select requiredPlugin).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourceDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourceDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourceDao.cs	(revision 12878)
@@ -0,0 +1,56 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class ResourceDao : GenericDao<Guid, Resource> {
+    public ResourceDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Resource GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public Resource GetByName(string name) {
+      return GetByNameQuery(DataContext, name);
+    }
+
+    public IQueryable<Resource> GetResourcesWithValidOwner() {
+      return Table.Where(x => x.OwnerUserId != null);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Resource> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid resourceId) =>
+        (from resource in db.GetTable<Resource>()
+         where resource.ResourceId == resourceId
+         select resource).SingleOrDefault());
+
+    private static readonly Func<DataContext, string, Resource> GetByNameQuery =
+     CompiledQuery.Compile((DataContext db, string name) =>
+       (from resource in db.GetTable<Resource>()
+        where resource.Name == name
+        select resource).FirstOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourcePermissionDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourcePermissionDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/ResourcePermissionDao.cs	(revision 12878)
@@ -0,0 +1,62 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class ResourcePermissionDao : GenericDao<Guid, ResourcePermission> {
+    public ResourcePermissionDao(DataContext dataContext) : base(dataContext) { }
+
+    public override ResourcePermission GetById(Guid id) {
+      throw new NotImplementedException();
+    }
+
+    public IEnumerable<ResourcePermission> GetByResourceId(Guid id) {
+      return GetByResourceIdGetByIdQuery(DataContext, id);
+    }
+
+    public void DeleteByResourceAndGrantedUserId(Guid resourceId, IEnumerable<Guid> grantedUserId) {
+      string paramIds = string.Join(",", grantedUserId.Select(x => string.Format("'{0}'", x)));
+      if (!string.IsNullOrWhiteSpace(paramIds)) {
+        string query = string.Format(DeleteByGrantedUserQuery, resourceId, paramIds);
+        DataContext.ExecuteCommand(query);
+      }
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, IEnumerable<ResourcePermission>> GetByResourceIdGetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid resourceId) =>
+        from resourcePermission in db.GetTable<ResourcePermission>()
+        where resourcePermission.ResourceId == resourceId
+        select resourcePermission);
+    #endregion
+
+    #region String queries
+    private const string DeleteByGrantedUserQuery =
+      @"DELETE FROM [ResourcePermission]
+         WHERE ResourceId = '{0}'
+           AND GrantedUserId IN ({1});";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveDao.cs	(revision 12878)
@@ -0,0 +1,164 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+using System.Linq.Expressions;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class SlaveDao : IGenericDao<Guid, Slave> {
+
+    private readonly DataContext dataContext;
+    private Table<Resource> Table {
+      get { return dataContext.GetTable<Resource>(); }
+    }
+
+    private IQueryable<Slave> Entities {
+      get { return Table.OfType<Slave>(); }
+    }
+
+    public SlaveDao(DataContext dataContext) {
+      this.dataContext = dataContext;
+    }
+
+    #region IGenericDao<Guid,Slave> Members
+    public Slave GetById(Guid id) {
+      return GetByIdQuery(dataContext, id);
+    }
+
+    public IQueryable<Slave> Get(Expression<Func<Slave, bool>> predicate) {
+      return Entities.Where(predicate);
+    }
+
+    public IQueryable<Slave> GetAll() {
+      return Entities;
+    }
+
+    public IQueryable<Slave> GetOnlineSlaves() {
+      return Entities.Where(x => x.SlaveState != SlaveState.Offline);
+    }
+
+    public Slave Save(Slave entity) {
+      Table.InsertOnSubmit(entity);
+      return entity;
+    }
+
+    public IEnumerable<Slave> Save(params Slave[] entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public IEnumerable<Slave> Save(IEnumerable<Slave> entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public Slave SaveOrAttach(Slave entity) {
+      if (Table.Contains(entity)) {
+        if (Table.GetOriginalEntityState(entity) == null) {
+          Table.Attach(entity);
+        }
+      } else {
+        Table.InsertOnSubmit(entity);
+      }
+      return entity;
+    }
+
+    public IEnumerable<Slave> SaveOrAttach(params Slave[] entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public IEnumerable<Slave> SaveOrAttach(IEnumerable<Slave> entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public void Delete(Guid id) {
+      Slave entity = GetById(id);
+      if (entity != null) {
+        Table.DeleteOnSubmit(entity);
+      }
+    }
+
+    public void Delete(IEnumerable<Guid> ids) {
+      foreach (var id in ids) {
+        Delete(id);
+      }
+    }
+
+    public void Delete(Slave entity) {
+      Table.DeleteOnSubmit(entity);
+    }
+
+    public void Delete(IEnumerable<Slave> entities) {
+      foreach (var entity in entities) {
+        Delete(entity);
+      }
+    }
+
+    public bool Exists(Slave entity) {
+      return ExistsQuery(dataContext, entity);
+    }
+
+    public bool Exists(Guid id) {
+      return GetById(id) != null;
+    }
+    #endregion
+
+    public bool SlaveHasToShutdownComputer(Guid slaveId) {
+      return dataContext.ExecuteQuery<int>(DowntimeQueryString, slaveId, DateTime.Now, DowntimeType.Shutdown.ToString()).FirstOrDefault() > 0;
+    }
+
+    public bool SlaveIsAllowedToCalculate(Guid slaveId) {
+      return dataContext.ExecuteQuery<int>(DowntimeQueryString, slaveId, DateTime.Now, DowntimeType.Offline.ToString()).FirstOrDefault() == 0;
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Slave> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid slaveId) =>
+        (from slave in db.GetTable<Resource>().OfType<Slave>()
+         where slave.ResourceId == slaveId
+         select slave).SingleOrDefault());
+
+    private static readonly Func<DataContext, Slave, bool> ExistsQuery =
+      CompiledQuery.Compile((DataContext db, Slave entity) =>
+        db.GetTable<Resource>().OfType<Slave>().Contains(entity));
+    #endregion
+
+    #region String queries
+    private const string DowntimeQueryString = @"
+      WITH pr AS (
+        SELECT ResourceId, ParentResourceId
+        FROM [Resource]
+        WHERE ResourceId = {0}
+        UNION ALL
+        SELECT r.ResourceId, r.ParentResourceId
+        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
+      )
+      SELECT COUNT(dt.DowntimeId)
+      FROM pr JOIN [Downtime] dt ON pr.ResourceId = dt.ResourceId
+      WHERE {1} BETWEEN dt.StartDate AND dt.EndDate
+        AND dt.DowntimeType = {2}
+    ";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveGroupDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveGroupDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/SlaveGroupDao.cs	(revision 12878)
@@ -0,0 +1,134 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+using System.Linq.Expressions;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class SlaveGroupDao : IGenericDao<Guid, SlaveGroup> {
+    private readonly DataContext dataContext;
+    private Table<Resource> Table {
+      get { return dataContext.GetTable<Resource>(); }
+    }
+
+    private IQueryable<SlaveGroup> Entities {
+      get { return Table.OfType<SlaveGroup>(); }
+    }
+
+    public SlaveGroupDao(DataContext dataContext) {
+      this.dataContext = dataContext;
+    }
+
+    #region IGenericDao<Guid,SlaveGroup> Members
+    public SlaveGroup GetById(Guid id) {
+      return GetByIdQuery(dataContext, id);
+    }
+
+    public IQueryable<SlaveGroup> Get(Expression<Func<SlaveGroup, bool>> predicate) {
+      return Entities.Where(predicate);
+    }
+
+    public IQueryable<SlaveGroup> GetAll() {
+      return Entities;
+    }
+
+    public SlaveGroup Save(SlaveGroup entity) {
+      Table.InsertOnSubmit(entity);
+      return entity;
+    }
+
+    public IEnumerable<SlaveGroup> Save(params SlaveGroup[] entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public IEnumerable<SlaveGroup> Save(IEnumerable<SlaveGroup> entities) {
+      return entities.Select(Save).ToList();
+    }
+
+    public SlaveGroup SaveOrAttach(SlaveGroup entity) {
+      if (Table.Contains(entity)) {
+        if (Table.GetOriginalEntityState(entity) == null) {
+          Table.Attach(entity);
+        }
+      } else {
+        Table.InsertOnSubmit(entity);
+      }
+      return entity;
+    }
+
+    public IEnumerable<SlaveGroup> SaveOrAttach(params SlaveGroup[] entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public IEnumerable<SlaveGroup> SaveOrAttach(IEnumerable<SlaveGroup> entities) {
+      return entities.Select(SaveOrAttach).ToList();
+    }
+
+    public void Delete(Guid id) {
+      SlaveGroup entity = GetById(id);
+      if (entity != null) {
+        Table.DeleteOnSubmit(entity);
+      }
+    }
+
+    public void Delete(IEnumerable<Guid> ids) {
+      foreach (var id in ids) {
+        Delete(id);
+      }
+    }
+
+    public void Delete(SlaveGroup entity) {
+      Table.DeleteOnSubmit(entity);
+    }
+
+    public void Delete(IEnumerable<SlaveGroup> entities) {
+      foreach (var entity in entities) {
+        Delete(entity);
+      }
+    }
+
+    public bool Exists(SlaveGroup entity) {
+      return ExistsQuery(dataContext, entity);
+    }
+
+    public bool Exists(Guid id) {
+      return GetById(id) != null;
+    }
+    #endregion
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, SlaveGroup> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid slaveGroupId) =>
+        (from slaveGroup in db.GetTable<Resource>().OfType<SlaveGroup>()
+         where slaveGroup.ResourceId == slaveGroupId
+         select slaveGroup).SingleOrDefault());
+
+    private static readonly Func<DataContext, SlaveGroup, bool> ExistsQuery =
+      CompiledQuery.Compile((DataContext db, SlaveGroup slaveGroup) =>
+        db.GetTable<Resource>().OfType<SlaveGroup>().Contains(slaveGroup));
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs	(revision 12878)
@@ -0,0 +1,57 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class StateLogDao : GenericDao<Guid, StateLog> {
+    private Table<Task> TaskTable {
+      get { return DataContext.GetTable<Task>(); }
+    }
+
+    public StateLogDao(DataContext dataContext) : base(dataContext) { }
+
+    public override StateLog GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public StateLog GetLastStateLogFromTask(Task task) {
+      return GetLastStateLogFromTaskQuery(DataContext, task);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, StateLog> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid stateLogId) =>
+        (from stateLog in db.GetTable<StateLog>()
+         where stateLog.StateLogId == stateLogId
+         select stateLog).SingleOrDefault());
+
+    private static readonly Func<DataContext, Task, StateLog> GetLastStateLogFromTaskQuery =
+      CompiledQuery.Compile((DataContext db, Task task) =>
+        (from stateLog in db.GetTable<StateLog>()
+         where stateLog.TaskId == task.TaskId
+         orderby stateLog.DateTime descending
+         select stateLog).First(x => x.SlaveId != null));
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 12878)
@@ -0,0 +1,124 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class TaskDao : GenericDao<Guid, Task> {
+    private Table<AssignedResource> AssignedResourceTable {
+      get { return DataContext.GetTable<AssignedResource>(); }
+    }
+
+    public TaskDao(DataContext dataContext) : base(dataContext) { }
+
+    public override Task GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    public IQueryable<Task> GetAllChildTasks() {
+      return Table.Where(x => !x.IsParentTask);
+    }
+
+    public IQueryable<Task> GetByJobId(Guid id) {
+      return Table.Where(x => x.JobId == id);
+    }
+    public class TaskPriorityInfo {
+      public Guid JobId { get; set; }
+      public Guid TaskId { get; set; }
+      public int Priority { get; set; }
+    }
+
+    public IEnumerable<TaskPriorityInfo> GetWaitingTasks(Slave slave) {
+      //Originally we checked here if there are parent tasks which should be calculated (with GetParentTasks(resourceIds, count, false);).
+      //Because there is at the moment no case where this makes sense (there don't exist parent tasks which need to be calculated), 
+      //we skip this step because it's wasted runtime
+      return DataContext.ExecuteQuery<TaskPriorityInfo>(GetWaitingTasksQueryString, slave.ResourceId, Enum.GetName(typeof(TaskState), TaskState.Waiting), slave.FreeCores, slave.FreeMemory).ToList();
+    }
+
+    /// <summary>
+    /// returns all parent tasks which are waiting for their child tasks to finish
+    /// </summary>
+    /// <param name="resourceIds">list of resourceids which for which the task should be valid</param>
+    /// <param name="count">maximum number of task to return</param>
+    /// <param name="finished">if true, all parent task which have FinishWhenChildJobsFinished=true are returned, otherwise only FinishWhenChildJobsFinished=false are returned</param>
+    /// <returns></returns>
+    public IEnumerable<Task> GetParentTasks(IEnumerable<Guid> resourceIds, int count, bool finished) {
+      var query = from ar in AssignedResourceTable
+                  where resourceIds.Contains(ar.ResourceId)
+                     && ar.Task.State == TaskState.Waiting
+                     && ar.Task.IsParentTask
+                     && (finished ? ar.Task.FinishWhenChildJobsFinished : !ar.Task.FinishWhenChildJobsFinished)
+                     && (from child in Table
+                         where child.ParentTaskId == ar.Task.TaskId
+                         select child.State == TaskState.Finished
+                             || child.State == TaskState.Aborted
+                             || child.State == TaskState.Failed).All(x => x)
+                     && (from child in Table // avoid returning WaitForChildTasks task where no child-task exist (yet)
+                         where child.ParentTaskId == ar.Task.TaskId
+                         select child).Any()
+                  orderby ar.Task.Priority descending
+                  select ar.Task;
+      return count == 0 ? query.ToArray() : query.Take(count).ToArray();
+    }
+
+    public void UpdateExecutionTime(Guid taskId, double executionTime) {
+      DataContext.ExecuteCommand(UpdateExecutionTimeQuery, executionTime, DateTime.Now, taskId);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, Task> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid taskId) =>
+        (from task in db.GetTable<Task>()
+         where task.TaskId == taskId
+         select task).SingleOrDefault());
+    #endregion
+
+    #region String queries
+    private const string GetWaitingTasksQueryString = @"
+      WITH pr AS (
+        SELECT ResourceId, ParentResourceId
+        FROM [Resource]
+        WHERE ResourceId = {0}
+        UNION ALL
+        SELECT r.ResourceId, r.ParentResourceId
+        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
+      )
+      SELECT DISTINCT t.TaskId, t.JobId, t.Priority
+      FROM pr JOIN AssignedResources ar ON ar.ResourceId = pr.ResourceId
+          JOIN Task t ON t.TaskId = ar.TaskId
+      WHERE NOT (t.IsParentTask = 1 AND t.FinishWhenChildJobsFinished = 1)
+          AND t.TaskState = {1}
+          AND t.CoresNeeded <= {2}
+          AND t.MemoryNeeded <= {3}
+    ";
+
+    private const string UpdateExecutionTimeQuery = @"
+      UPDATE [Task] 
+         SET ExecutionTimeMs = {0},
+             LastHeartbeat = {1} 
+       WHERE TaskId = {2}
+    ";
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class TaskDataDao : GenericDao<Guid, TaskData> {
+    public TaskDataDao(DataContext dataContext) : base(dataContext) { }
+
+    public override TaskData GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, TaskData> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid taskId) =>
+        (from taskData in db.GetTable<TaskData>()
+         where taskData.TaskId == taskId
+         select taskData).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/UserPriorityDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/UserPriorityDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/UserPriorityDao.cs	(revision 12878)
@@ -0,0 +1,42 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Daos {
+  public class UserPriorityDao : GenericDao<Guid, UserPriority> {
+    public UserPriorityDao(DataContext dataContext) : base(dataContext) { }
+
+    public override UserPriority GetById(Guid id) {
+      return GetByIdQuery(DataContext, id);
+    }
+
+    #region Compiled queries
+    private static readonly Func<DataContext, Guid, UserPriority> GetByIdQuery =
+      CompiledQuery.Compile((DataContext db, Guid userId) =>
+        (from userPriority in db.GetTable<UserPriority>()
+         where userPriority.UserId == userId
+         select userPriority).SingleOrDefault());
+    #endregion
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Data/TableInformation.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Data/TableInformation.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Data/TableInformation.cs	(revision 12878)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.Hive.DataAccess.Data {
+  public class TableInformation {
+    public string Name { get; set; }
+    public int Rows { get; set; }
+    public int Reserved { get; set; }
+    public int Data { get; set; }
+    public int IndexSize { get; set; }
+    public int Unused { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HeuristicLab.Services.Hive.DataAccess-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HeuristicLab.Services.Hive.DataAccess-3.3.csproj	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HeuristicLab.Services.Hive.DataAccess-3.3.csproj	(revision 12878)
@@ -104,7 +104,34 @@
   <ItemGroup>
     <None Include="Plugin.cs.frame" />
+    <Compile Include="Daos\AssignedResourceDao.cs" />
+    <Compile Include="Daos\DowntimeDao.cs" />
+    <Compile Include="Daos\GenericDao.cs" />
+    <Compile Include="Daos\HiveStatistics\DimClientDao.cs" />
+    <Compile Include="Daos\HiveStatistics\DimJobDao.cs" />
+    <Compile Include="Daos\HiveStatistics\DimTimeDao.cs" />
+    <Compile Include="Daos\HiveStatistics\DimUserDao.cs" />
+    <Compile Include="Daos\HiveStatistics\FactClientInfoDao.cs" />
+    <Compile Include="Daos\HiveStatistics\FactTaskDao.cs" />
+    <Compile Include="Daos\JobDao.cs" />
+    <Compile Include="Daos\JobPermissionDao.cs" />
+    <Compile Include="Daos\LifecycleDao.cs" />
+    <Compile Include="Daos\PluginDao.cs" />
+    <Compile Include="Daos\PluginDataDao.cs" />
+    <Compile Include="Daos\RequiredPluginDao.cs" />
+    <Compile Include="Daos\ResourceDao.cs" />
+    <Compile Include="Daos\ResourcePermissionDao.cs" />
+    <Compile Include="Daos\SlaveDao.cs" />
+    <Compile Include="Daos\SlaveGroupDao.cs" />
+    <Compile Include="Daos\StateLogDao.cs" />
+    <Compile Include="Daos\TaskDao.cs" />
+    <Compile Include="Daos\TaskDataDao.cs" />
+    <Compile Include="Daos\UserPriorityDao.cs" />
+    <Compile Include="Data\TableInformation.cs" />
+    <Compile Include="Interfaces\IGenericDao.cs" />
     <Compile Include="Enums\Command.cs" />
     <Compile Include="Enums\DowntimeType.cs" />
     <Compile Include="Enums\CpuArchitecture.cs" />
+    <Compile Include="Interfaces\IPersistenceManager.cs" />
+    <Compile Include="Manager\PersistenceManager.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="HiveDataContext.cs">
@@ -116,5 +143,4 @@
       <DependentUpon>HiveDataContext.dbml</DependentUpon>
     </Compile>
-    <Compile Include="ITransactionManager.cs" />
     <Compile Include="Enums\TaskState.cs" />
     <Compile Include="Logger.cs" />
@@ -128,5 +154,4 @@
     </Compile>
     <Compile Include="Enums\SlaveState.cs" />
-    <Compile Include="TransactionManager.cs" />
   </ItemGroup>
   <ItemGroup>
@@ -155,7 +180,7 @@
   <ItemGroup>
     <Content Include="SQL Scripts\Initialize Hive Database.sql" />
-    <Content Include="SQL Scripts\Migrate to Filestream.sql" />
     <Content Include="SQL Scripts\Prepare Hive Database.sql" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,830 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using HeuristicLab.Services.Hive.Common.DataTransfer;
-using DT = HeuristicLab.Services.Hive.Common.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public class HiveDao : IHiveDao {
-    public static HiveDataContext CreateContext(bool longRunning = false) {
-      var context = new HiveDataContext(Settings.Default.HeuristicLab_Hive_LinqConnectionString);
-      if (longRunning) context.CommandTimeout = (int)Settings.Default.LongRunningDatabaseCommandTimeout.TotalSeconds;
-      return context;
-    }
-
-    public HiveDao() { }
-
-    #region Job Methods
-    public DT.Job GetJob(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Jobs.SingleOrDefault(x => x.JobId == id));
-      }
-    }
-
-    public IEnumerable<DT.Job> GetJobs(Expression<Func<Job, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Jobs.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddJob(DT.Job dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Jobs.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        foreach (Guid pluginId in dto.PluginsNeededIds) {
-          db.RequiredPlugins.InsertOnSubmit(new RequiredPlugin() { JobId = entity.JobId, PluginId = pluginId });
-        }
-        db.SubmitChanges();
-        return entity.JobId;
-      }
-    }
-
-    public void UpdateJob(DT.Job dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Jobs.FirstOrDefault(x => x.JobId == dto.Id);
-        if (entity == null) db.Jobs.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        foreach (Guid pluginId in dto.PluginsNeededIds) {
-          if (db.RequiredPlugins.Count(p => p.PluginId == pluginId) == 0) {
-            db.RequiredPlugins.InsertOnSubmit(new RequiredPlugin() { JobId = entity.JobId, PluginId = pluginId });
-          }
-        }
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteJob(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Jobs.FirstOrDefault(x => x.JobId == id);
-        if (entity != null) db.Jobs.DeleteOnSubmit(entity);
-        db.SubmitChanges(); // JobData and child jobs are deleted by db-trigger
-      }
-    }
-
-    /// <summary>
-    /// returns all parent jobs which are waiting for their child jobs to finish
-    /// </summary>
-    /// <param name="resourceIds">list of resourceids which for which the jobs should be valid</param>
-    /// <param name="count">maximum number of jobs to return</param>
-    /// <param name="finished">if true, all parent jobs which have FinishWhenChildJobsFinished=true are returned, otherwise only FinishWhenChildJobsFinished=false are returned</param>
-    /// <returns></returns>
-    public IEnumerable<DT.Job> GetParentJobs(IEnumerable<Guid> resourceIds, int count, bool finished) {
-      using (var db = CreateContext()) {
-        var query = from ar in db.AssignedResources
-                    where resourceIds.Contains(ar.ResourceId)
-                       && ar.Job.State == JobState.Waiting
-                       && ar.Job.IsParentJob
-                       && (finished ? ar.Job.FinishWhenChildJobsFinished : !ar.Job.FinishWhenChildJobsFinished)
-                       && (from child in db.Jobs
-                           where child.ParentJobId == ar.Job.JobId
-                           select child.State == JobState.Finished
-                               || child.State == JobState.Aborted
-                               || child.State == JobState.Failed).All(x => x)
-                       && (from child in db.Jobs // avoid returning WaitForChildJobs jobs where no child-jobs exist (yet)
-                           where child.ParentJobId == ar.Job.JobId
-                           select child).Count() > 0
-                    orderby ar.Job.Priority descending, db.Random()
-                    select Convert.ToDto(ar.Job);
-        return count == 0 ? query.ToArray() : query.Take(count).ToArray();
-      }
-    }
-
-    public IEnumerable<DT.Job> GetWaitingJobs(DT.Slave slave, int count) {
-      using (var db = CreateContext()) {
-        var resourceIds = GetParentResources(slave.Id).Select(r => r.Id);
-        var waitingParentJobs = GetParentJobs(resourceIds, count, false);
-        if (count > 0 && waitingParentJobs.Count() >= count) return waitingParentJobs.Take(count).ToArray();
-
-        var query = from ar in db.AssignedResources
-                    where resourceIds.Contains(ar.ResourceId)
-                       && !(ar.Job.IsParentJob && ar.Job.FinishWhenChildJobsFinished)
-                       && ar.Job.State == JobState.Waiting
-                       && ar.Job.CoresNeeded <= slave.FreeCores
-                       && ar.Job.MemoryNeeded <= slave.FreeMemory
-                    orderby ar.Job.Priority descending, db.Random() // take random job to avoid the race condition that occurs when this method is called concurrently (the same job would be returned)
-                    select Convert.ToDto(ar.Job);
-        var waitingJobs = (count == 0 ? query : query.Take(count)).ToArray();
-        return waitingJobs.Union(waitingParentJobs).OrderByDescending(x => x.Priority);
-      }
-    }
-
-    public DT.Job UpdateJobState(Guid jobId, JobState jobState, Guid? slaveId, Guid? userId, string exception) {
-      using (var db = CreateContext()) {
-        var job = db.Jobs.SingleOrDefault(x => x.JobId == jobId);
-        job.State = jobState;
-        db.StateLogs.InsertOnSubmit(new StateLog {
-          JobId = jobId,
-          State = jobState,
-          SlaveId = slaveId,
-          UserId = userId,
-          Exception = exception,
-          DateTime = DateTime.Now
-        });
-        db.SubmitChanges();
-        job = db.Jobs.SingleOrDefault(x => x.JobId == jobId);
-        return Convert.ToDto(job);
-      }
-    }
-    #endregion
-
-    #region JobData Methods
-    public DT.JobData GetJobData(Guid id) {
-      using (var db = CreateContext(true)) {
-        return Convert.ToDto(db.JobDatas.SingleOrDefault(x => x.JobId == id));
-      }
-    }
-
-    public IEnumerable<DT.JobData> GetJobDatas(Expression<Func<JobData, bool>> predicate) {
-      using (var db = CreateContext(true)) {
-        return db.JobDatas.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddJobData(DT.JobData dto) {
-      using (var db = CreateContext(true)) {
-        var entity = Convert.ToEntity(dto);
-        db.JobDatas.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.JobId;
-      }
-    }
-
-    public void UpdateJobData(DT.JobData dto) {
-      using (var db = CreateContext(true)) {
-        var entity = db.JobDatas.FirstOrDefault(x => x.JobId == dto.JobId);
-        if (entity == null) db.JobDatas.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteJobData(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.JobDatas.FirstOrDefault(x => x.JobId == id); // check if all the byte[] is loaded into memory here. otherwise work around to delete without loading it
-        if (entity != null) db.JobDatas.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region StateLog Methods
-    public DT.StateLog GetStateLog(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.StateLogs.SingleOrDefault(x => x.StateLogId == id));
-      }
-    }
-
-    public IEnumerable<DT.StateLog> GetStateLogs(Expression<Func<StateLog, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.StateLogs.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddStateLog(DT.StateLog dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.StateLogs.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.StateLogId;
-      }
-    }
-
-    public void UpdateStateLog(DT.StateLog dto) {
-      using (var db = CreateContext()) {
-        var entity = db.StateLogs.FirstOrDefault(x => x.StateLogId == dto.Id);
-        if (entity == null) db.StateLogs.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteStateLog(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.StateLogs.FirstOrDefault(x => x.StateLogId == id);
-        if (entity != null) db.StateLogs.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region HiveExperiment Methods
-    public DT.HiveExperiment GetHiveExperiment(Guid id) {
-      using (var db = CreateContext()) {
-        return AddStatsToExperiment(db, Convert.ToDto(db.HiveExperiments.SingleOrDefault(x => x.HiveExperimentId == id)));
-      }
-    }
-
-    private DT.HiveExperiment AddStatsToExperiment(HiveDataContext db, DT.HiveExperiment exp) {
-      if (exp == null)
-        return null;
-
-      var jobs = db.Jobs.Where(j => j.HiveExperimentId == exp.Id);
-      exp.JobCount = jobs.Count();
-      exp.CalculatingCount = jobs.Count(j => j.State == JobState.Calculating);
-      exp.FinishedCount = jobs.Count(j => j.State == JobState.Finished);
-      return exp;
-    }
-
-    public IEnumerable<DT.HiveExperiment> GetHiveExperiments(Expression<Func<HiveExperiment, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.HiveExperiments.Where(predicate).Select(x => AddStatsToExperiment(db, Convert.ToDto(x))).ToArray();
-      }
-    }
-
-    public Guid AddHiveExperiment(DT.HiveExperiment dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.HiveExperiments.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.HiveExperimentId;
-      }
-    }
-
-    public void UpdateHiveExperiment(DT.HiveExperiment dto) {
-      using (var db = CreateContext()) {
-        var entity = db.HiveExperiments.FirstOrDefault(x => x.HiveExperimentId == dto.Id);
-        if (entity == null) db.HiveExperiments.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteHiveExperiment(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.HiveExperiments.FirstOrDefault(x => x.HiveExperimentId == id);
-        if (entity != null) db.HiveExperiments.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region HiveExperimentPermission Methods
-    public DT.HiveExperimentPermission GetHiveExperimentPermission(Guid hiveExperimentId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.HiveExperimentPermissions.SingleOrDefault(x => x.HiveExperimentId == hiveExperimentId && x.GrantedUserId == grantedUserId));
-      }
-    }
-
-    public IEnumerable<DT.HiveExperimentPermission> GetHiveExperimentPermissions(Expression<Func<HiveExperimentPermission, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.HiveExperimentPermissions.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public void AddHiveExperimentPermission(DT.HiveExperimentPermission dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.HiveExperimentPermissions.InsertOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void UpdateHiveExperimentPermission(DT.HiveExperimentPermission dto) {
-      using (var db = CreateContext()) {
-        var entity = db.HiveExperimentPermissions.FirstOrDefault(x => x.HiveExperimentId == dto.HiveExperimentId && x.GrantedUserId == dto.GrantedUserId);
-        if (entity == null) db.HiveExperimentPermissions.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteHiveExperimentPermission(Guid hiveExperimentId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        var entity = db.HiveExperimentPermissions.FirstOrDefault(x => x.HiveExperimentId == hiveExperimentId && x.GrantedUserId == grantedUserId);
-        if (entity != null) db.HiveExperimentPermissions.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    /// <summary>
-    /// Sets the permissions for a experiment. makes sure that only one permission per user exists.
-    /// </summary>
-    public void SetHiveExperimentPermission(Guid hiveExperimentId, Guid grantedByUserId, Guid grantedUserId, Permission permission) {
-      using (var db = CreateContext()) {
-        HiveExperimentPermission hiveExperimentPermission = db.HiveExperimentPermissions.SingleOrDefault(x => x.HiveExperimentId == hiveExperimentId && x.GrantedUserId == grantedUserId);
-        if (hiveExperimentPermission != null) {
-          if (permission == Permission.NotAllowed) {
-            // not allowed, delete
-            db.HiveExperimentPermissions.DeleteOnSubmit(hiveExperimentPermission);
-          } else {
-            // update
-            hiveExperimentPermission.Permission = permission;
-            hiveExperimentPermission.GrantedByUserId = grantedByUserId; // update grantedByUserId, always the last "granter" is stored
-          }
-        } else {
-          // insert
-          if (permission != Permission.NotAllowed) {
-            hiveExperimentPermission = new HiveExperimentPermission() { HiveExperimentId = hiveExperimentId, GrantedByUserId = grantedByUserId, GrantedUserId = grantedUserId, Permission = permission };
-            db.HiveExperimentPermissions.InsertOnSubmit(hiveExperimentPermission);
-          }
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Plugin Methods
-    public DT.Plugin GetPlugin(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Plugins.SingleOrDefault(x => x.PluginId == id));
-      }
-    }
-
-    public IEnumerable<DT.Plugin> GetPlugins(Expression<Func<Plugin, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Plugins.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddPlugin(DT.Plugin dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Plugins.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.PluginId;
-      }
-    }
-
-    public void UpdatePlugin(DT.Plugin dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Plugins.FirstOrDefault(x => x.PluginId == dto.Id);
-        if (entity == null) db.Plugins.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeletePlugin(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Plugins.FirstOrDefault(x => x.PluginId == id);
-        if (entity != null) db.Plugins.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region PluginData Methods
-    public DT.PluginData GetPluginData(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.PluginDatas.SingleOrDefault(x => x.PluginDataId == id));
-      }
-    }
-
-    public IEnumerable<DT.PluginData> GetPluginDatas(Expression<Func<PluginData, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.PluginDatas.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddPluginData(DT.PluginData dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.PluginDatas.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.PluginDataId;
-      }
-    }
-
-    public void UpdatePluginData(DT.PluginData dto) {
-      using (var db = CreateContext()) {
-        var entity = db.PluginDatas.FirstOrDefault(x => x.PluginId == dto.PluginId);
-        if (entity == null) db.PluginDatas.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeletePluginData(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.PluginDatas.FirstOrDefault(x => x.PluginDataId == id);
-        if (entity != null) db.PluginDatas.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Slave Methods
-    public DT.Slave GetSlave(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Resources.OfType<Slave>().SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.Slave> GetSlaves(Expression<Func<Slave, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.OfType<Slave>().Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddSlave(DT.Slave dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateSlave(DT.Slave dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<Slave>().FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteSlave(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<Slave>().FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) db.Resources.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region SlaveGroup Methods
-    public DT.SlaveGroup GetSlaveGroup(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Resources.OfType<SlaveGroup>().SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.SlaveGroup> GetSlaveGroups(Expression<Func<SlaveGroup, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.OfType<SlaveGroup>().Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddSlaveGroup(DT.SlaveGroup dto) {
-      using (var db = CreateContext()) {
-        if (dto.Id == Guid.Empty)
-          dto.Id = Guid.NewGuid();
-        var entity = Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateSlaveGroup(DT.SlaveGroup dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<SlaveGroup>().FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteSlaveGroup(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<SlaveGroup>().FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) {
-          if (db.Resources.Where(r => r.ParentResourceId == id).Count() > 0) {
-            throw new InvalidOperationException("Cannot delete SlaveGroup as long as there are Slaves in the group");
-          }
-          db.Resources.DeleteOnSubmit(entity);
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Resource Methods
-    public DT.Resource GetResource(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Resources.SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.Resource> GetResources(Expression<Func<Resource, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddResource(DT.Resource dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateResource(DT.Resource dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteResource(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) db.Resources.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void AssignJobToResource(Guid jobId, Guid resourceId) {
-      using (var db = CreateContext()) {
-        var job = db.Jobs.Where(x => x.JobId == jobId).Single();
-        job.AssignedResources.Add(new AssignedResource() { JobId = jobId, ResourceId = resourceId });
-        db.SubmitChanges();
-      }
-    }
-
-    public IEnumerable<DT.Resource> GetAssignedResources(Guid jobId) {
-      using (var db = CreateContext()) {
-        var job = db.Jobs.Where(x => x.JobId == jobId).Single();
-        return job.AssignedResources.Select(x => Convert.ToDto(x.Resource)).ToArray();
-      }
-    }
-
-    /// <summary>
-    /// Returns all parent resources of a resource (the given resource is also added)
-    /// </summary>
-    public IEnumerable<DT.Resource> GetParentResources(Guid resourceId) {
-      using (var db = CreateContext()) {
-        var resources = new List<Resource>();
-        CollectParentResources(resources, db.Resources.Where(r => r.ResourceId == resourceId).Single());
-        return resources.Select(r => Convert.ToDto(r)).ToArray();
-      }
-    }
-
-    private void CollectParentResources(List<Resource> resources, Resource resource) {
-      if (resource == null) return;
-      resources.Add(resource);
-      CollectParentResources(resources, resource.ParentResource);
-    }
-
-    /// <summary>
-    /// Returns all child resources of a resource (without the given resource)
-    /// </summary>
-    public IEnumerable<DT.Resource> GetChildResources(Guid resourceId) {
-      using (var db = CreateContext()) {
-        var childs = new List<DT.Resource>();
-        foreach (var child in db.Resources.Where(x => x.ParentResourceId == resourceId)) {
-          childs.Add(Convert.ToDto(child));
-          childs.AddRange(GetChildResources(child.ResourceId));
-        }
-        return childs;
-      }
-    }
-
-    public IEnumerable<DT.Job> GetJobsByResourceId(Guid resourceId) {
-      using (var db = CreateContext()) {
-        var resources = GetChildResources(resourceId).Select(x => x.Id).ToList();
-        resources.Add(resourceId);
-
-        var jobs = db.Jobs.Where(j =>
-          j.State == JobState.Calculating &&
-          j.StateLogs.OrderByDescending(x => x.DateTime).First().SlaveId.HasValue &&
-          resources.Contains(j.StateLogs.OrderByDescending(x => x.DateTime).First().SlaveId.Value));
-        return jobs.Select(j => Convert.ToDto(j)).ToArray();
-      }
-    }
-    #endregion
-
-    #region Authorization Methods
-    public Permission GetPermissionForJob(Guid jobId, Guid userId) {
-      using (var db = CreateContext()) {
-        return GetPermissionForExperiment(GetExperimentForJob(jobId), userId);
-      }
-    }
-
-    public Permission GetPermissionForExperiment(Guid experimentId, Guid userId) {
-      using (var db = CreateContext()) {
-        HiveExperiment hiveExperiment = db.HiveExperiments.SingleOrDefault(x => x.HiveExperimentId == experimentId);
-        if (hiveExperiment == null) return Permission.NotAllowed;
-        if (hiveExperiment.OwnerUserId == userId) return Permission.Full;
-        HiveExperimentPermission permission = db.HiveExperimentPermissions.SingleOrDefault(p => p.HiveExperimentId == experimentId && p.GrantedUserId == userId);
-        return permission != null ? permission.Permission : Permission.NotAllowed;
-      }
-    }
-
-    public Guid GetExperimentForJob(Guid jobId) {
-      using (var db = CreateContext()) {
-        return db.Jobs.Single(j => j.JobId == jobId).HiveExperimentId;
-      }
-    }
-    #endregion
-
-    #region Lifecycle Methods
-    public DateTime GetLastCleanup() {
-      using (var db = CreateContext()) {
-        var entity = db.Lifecycles.SingleOrDefault();
-        return entity != null ? entity.LastCleanup : DateTime.MinValue;
-      }
-    }
-
-    public void SetLastCleanup(DateTime datetime) {
-      using (var db = CreateContext()) {
-        var entity = db.Lifecycles.SingleOrDefault();
-        if (entity != null) {
-          entity.LastCleanup = datetime;
-        } else {
-          entity = new Lifecycle();
-          entity.LifecycleId = 0; // always only one entry with ID:0
-          entity.LastCleanup = datetime;
-          db.Lifecycles.InsertOnSubmit(entity);
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Downtime Methods
-    public DT.Downtime GetDowntime(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Downtimes.SingleOrDefault(x => x.DowntimeId == id));
-      }
-    }
-
-    public IEnumerable<DT.Downtime> GetDowntimes(Expression<Func<Downtime, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Downtimes.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddDowntime(DT.Downtime dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Downtimes.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.DowntimeId;
-      }
-    }
-
-    public void UpdateDowntime(DT.Downtime dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Downtimes.FirstOrDefault(x => x.DowntimeId == dto.Id);
-        if (entity == null) db.Downtimes.InsertOnSubmit(Convert.ToEntity(dto));
-        else Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteDowntime(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Downtimes.FirstOrDefault(x => x.DowntimeId == id);
-        if (entity != null) db.Downtimes.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Statistics Methods
-    public DT.Statistics GetStatistic(Guid id) {
-      using (var db = CreateContext()) {
-        return Convert.ToDto(db.Statistics.SingleOrDefault(x => x.StatisticsId == id));
-      }
-    }
-
-    public IEnumerable<DT.Statistics> GetStatistics(Expression<Func<Statistics, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Statistics.Where(predicate).Select(x => Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddStatistics(DT.Statistics dto) {
-      using (var db = CreateContext()) {
-        var entity = Convert.ToEntity(dto);
-        db.Statistics.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        foreach (var slaveStat in dto.SlaveStatistics) {
-          slaveStat.Id = entity.StatisticsId;
-          db.SlaveStatistics.InsertOnSubmit(Convert.ToEntity(slaveStat));
-        }
-        foreach (var userStat in dto.UserStatistics) {
-          userStat.Id = entity.StatisticsId;
-          db.UserStatistics.InsertOnSubmit(Convert.ToEntity(userStat));
-        }
-        db.SubmitChanges();
-        return entity.StatisticsId;
-      }
-    }
-
-    public void DeleteStatistics(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Statistics.FirstOrDefault(x => x.StatisticsId == id);
-        if (entity != null) db.Statistics.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public List<DT.UserStatistics> GetUserStatistics() {
-      using (var db = CreateContext()) {
-        var userStats = new Dictionary<Guid, DT.UserStatistics>();
-
-        var usedCoresByUser = from job in db.Jobs
-                              where job.State == JobState.Calculating
-                              group job by job.HiveExperiment.OwnerUserId into g
-                              select new { UserId = g.Key, UsedCores = g.Count() };
-
-        foreach (var item in usedCoresByUser) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].UsedCores += item.UsedCores;
-        }
-
-        var executionTimesByUser = from job in db.Jobs
-                                   group job by job.HiveExperiment.OwnerUserId into g
-                                   select new { UserId = g.Key, ExecutionTime = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMs).Sum()) };
-        foreach (var item in executionTimesByUser) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTime += item.ExecutionTime;
-        }
-
-        // execution times only of finished jobs - necessary to compute efficieny
-        var executionTimesFinishedJobs = from job in db.Jobs
-                                         where job.State == JobState.Finished
-                                         group job by job.HiveExperiment.OwnerUserId into g
-                                         select new { UserId = g.Key, ExecutionTimeFinishedJobs = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMs).Sum()) };
-
-        foreach (var item in executionTimesFinishedJobs) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTimeFinishedJobs += item.ExecutionTimeFinishedJobs;
-        }
-
-        // start to end times only of finished jobs - necessary to compute efficiency
-        var startToEndTimesFinishedJobs = from job in db.Jobs
-                                          where job.State == JobState.Finished
-                                          group job by job.HiveExperiment.OwnerUserId into g
-                                          select new {
-                                            UserId = g.Key,
-                                            StartToEndTime = new TimeSpan(g.Select(x => x.StateLogs.OrderByDescending(sl => sl.DateTime).First().DateTime - x.StateLogs.OrderBy(sl => sl.DateTime).First().DateTime).Sum(ts => ts.Ticks))
-                                          };
-        foreach (var item in startToEndTimesFinishedJobs) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].StartToEndTime += item.StartToEndTime;
-        }
-
-        // also consider executiontimes of DeletedJobStats 
-        var deletedJobsExecutionTimesByUsers = from del in db.DeletedJobStatistics
-                                               group del by del.UserId into g
-                                               select new {
-                                                 UserId = g.Key,
-                                                 ExecutionTime = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMs).Sum()),
-                                                 ExecutionTimeFinishedJobs = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMsFinishedJobs).Sum()),
-                                                 StartToEndTime = TimeSpan.FromMilliseconds(g.Select(x => x.StartToEndTimeMs).Sum())
-                                               };
-        foreach (var item in deletedJobsExecutionTimesByUsers) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTime += item.ExecutionTime;
-          userStats[item.UserId].ExecutionTimeFinishedJobs += item.ExecutionTimeFinishedJobs;
-          userStats[item.UserId].StartToEndTime += item.StartToEndTime;
-        }
-
-        return userStats.Values.ToList();
-      }
-    }
-    #endregion
-
-    #region Helpers
-    private void CollectChildJobs(HiveDataContext db, Guid parentJobId, List<Job> collection) {
-      var jobs = db.Jobs.Where(j => j.ParentJobId == parentJobId);
-      foreach (var job in jobs) {
-        collection.Add(job);
-        if (job.IsParentJob)
-          CollectChildJobs(db, job.JobId, collection);
-      }
-    }
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.cs	(revision 12878)
@@ -30,4 +30,8 @@
       throw new NotImplementedException();
     }
+
+    partial void OnCreated() {
+      LogFactory.GetLogger(this.GetType().Namespace).Log("HiveDataContext created");
+    }
   }
 }
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml	(revision 12878)
@@ -156,44 +156,4 @@
     </Type>
   </Table>
-  <Table Name="" Member="DeletedJobStatistics">
-    <Type Name="DeletedJobStatistics">
-      <Column Name="UserId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
-      <Column Name="ExecutionTimeS" Storage="_ExecutionTime" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="ExecutionTimeSFinishedJobs" Storage="_ExecutionTimeFinishedJobs" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="StartToEndTimeS" Storage="_StartToEndTime" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="DeletedJobStatisticsId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
-    </Type>
-  </Table>
-  <Table Name="" Member="UserStatistics">
-    <Type Name="UserStatistics">
-      <Column Name="StatisticsId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
-      <Column Name="UserId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
-      <Column Name="ExecutionTimeMs" Storage="_ExecutionTime" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="UsedCores" Storage="_CoresUsed" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
-      <Column Name="ExecutionTimeMsFinishedJobs" Storage="_ExecutionTimeFinishedJobs" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="StartToEndTimeMs" Storage="_StartToEndTime" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Association Name="Statistics_UserStatistics" Member="Statistics" ThisKey="StatisticsId" OtherKey="StatisticsId" Type="Statistics" IsForeignKey="true" />
-    </Type>
-  </Table>
-  <Table Name="" Member="SlaveStatistics">
-    <Type Name="SlaveStatistics">
-      <Column Name="StatisticsId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
-      <Column Name="SlaveId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
-      <Column Name="Cores" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
-      <Column Name="FreeCores" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
-      <Column Name="CpuUtilization" Type="System.Double" DbType="float NOT NULL" CanBeNull="false" />
-      <Column Name="Memory" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
-      <Column Name="FreeMemory" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
-      <Association Name="Statistics_SlaveStatistics" Member="Statistics" ThisKey="StatisticsId" OtherKey="StatisticsId" Type="Statistics" IsForeignKey="true" />
-    </Type>
-  </Table>
-  <Table Name="" Member="Statistics">
-    <Type Name="Statistics">
-      <Column Name="StatisticsId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
-      <Column Name="Timestamp" Type="System.DateTime" CanBeNull="false" />
-      <Association Name="Statistics_UserStatistics" Member="UserStatistics" ThisKey="StatisticsId" OtherKey="StatisticsId" Type="UserStatistics" />
-      <Association Name="Statistics_SlaveStatistics" Member="SlaveStatistics" ThisKey="StatisticsId" OtherKey="StatisticsId" Type="SlaveStatistics" />
-    </Type>
-  </Table>
   <Table Name="dbo.ResourcePermission" Member="ResourcePermissions">
     <Type Name="ResourcePermission">
@@ -210,3 +170,90 @@
     </Type>
   </Table>
+  <Table Name="[statistics].DimClient" Member="DimClients">
+    <Type Name="DimClient">
+      <Column Name="Id" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
+      <Column Name="Name" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
+      <Column Name="ResourceId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
+      <Column Name="ExpirationTime" Type="System.DateTime" DbType="DateTime" CanBeNull="true" />
+      <Column Name="ResourceGroupId" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
+      <Column Name="ResourceGroup2Id" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
+      <Column Name="GroupName" Type="System.String" CanBeNull="true" />
+      <Column Name="GroupName2" Type="System.String" CanBeNull="true" />
+      <Association Name="DimClient_FactTask" Member="FactTasks" ThisKey="Id" OtherKey="LastClientId" Type="FactTask" />
+      <Association Name="DimClient_FactClientInfo" Member="FactClientInfos" ThisKey="Id" OtherKey="ClientId" Type="FactClientInfo" />
+    </Type>
+  </Table>
+  <Table Name="[statistics].FactTask" Member="FactTasks">
+    <Type Name="FactTask">
+      <Column Name="TaskId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="CalculatingTime" Storage="_TotalRuntime" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="WaitingTime" Storage="_TotalWaitingTime" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="TransferTime" Storage="_TotalTransferTime" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="NumCalculationRuns" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="NumRetries" Storage="_NumFails" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="CoresRequired" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="MemoryRequired" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="Priority" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="LastClientId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="true" />
+      <Column Name="JobId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
+      <Column Name="EndTime" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="true" />
+      <Column Name="TaskState" Type="global::HeuristicLab.Services.Hive.DataAccess.TaskState" DbType="varchar(30)" CanBeNull="false" />
+      <Column Name="Exception" Type="System.String" DbType="varchar(MAX)" CanBeNull="true" />
+      <Column Name="InitialWaitingTime" Storage="_InitialWaitingTimeMs" Type="System.Int64" DbType="INT" CanBeNull="false" />
+      <Column Name="StartTime" Type="System.DateTime" CanBeNull="true" />
+      <Association Name="DimClient_FactTask" Member="DimClient" ThisKey="LastClientId" OtherKey="Id" Type="DimClient" IsForeignKey="true" />
+      <Association Name="DimJob_FactTask" Member="DimJob" ThisKey="JobId" OtherKey="JobId" Type="DimJob" IsForeignKey="true" />
+    </Type>
+  </Table>
+  <Table Name="[statistics].DimJob" Member="DimJobs">
+    <Type Name="DimJob">
+      <Column Name="JobId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="UserId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
+      <Column Name="JobName" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
+      <Column Name="UserName" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
+      <Column Name="DateCreated" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
+      <Column Name="TotalTasks" Type="System.Int32" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="CompletedTasks" Type="System.Int32" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="DateCompleted" Type="System.DateTime" DbType="DateTime NULL" CanBeNull="true" />
+      <Association Name="DimJob_FactTask" Member="FactTasks" ThisKey="JobId" OtherKey="JobId" Type="FactTask" />
+    </Type>
+  </Table>
+  <Table Name="[statistics].DimTime" Member="DimTimes">
+    <Type Name="DimTime">
+      <Column Name="Time" Type="System.DateTime" DbType="DateTime NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="Hour" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
+      <Column Name="Day" Type="System.DateTime" DbType="Date NOT NULL" CanBeNull="false" />
+      <Column Name="Month" Type="System.DateTime" DbType="Date NOT NULL" CanBeNull="false" />
+      <Column Name="Year" Type="System.DateTime" DbType="Date NOT NULL" CanBeNull="false" />
+      <Column Name="Minute" Type="System.DateTime" CanBeNull="false" />
+      <Association Name="DimTime_FactClientInfo" Member="FactClientInfos" ThisKey="Time" OtherKey="Time" Type="FactClientInfo" />
+    </Type>
+  </Table>
+  <Table Name="[statistics].DimUser" Member="DimUsers">
+    <Type Name="DimUser">
+      <Column Name="UserId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="Name" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
+      <Association Name="DimUser_FactClientInfo" Member="FactClientInfos" ThisKey="UserId" OtherKey="UserId" Type="FactClientInfo" />
+    </Type>
+  </Table>
+  <Table Name="[statistics].FactClientInfo" Member="FactClientInfos">
+    <Type Name="FactClientInfo">
+      <Column Name="ClientId" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="Time" Type="System.DateTime" DbType="DateTime NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="UserId" Type="System.Guid" DbType="UniqueIdentifier NULL" IsPrimaryKey="true" CanBeNull="false" />
+      <Column Name="NumUsedCores" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="NumTotalCores" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="UsedMemory" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="TotalMemory" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
+      <Column Name="CpuUtilization" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
+      <Column Name="IdleTime" Storage="_TotalTimeIdle" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="OfflineTime" Storage="_TotalTimeOffline" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="UnavailableTime" Storage="_TotalTimeUnavailable" Type="System.Int64" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="SlaveState" Type="global::HeuristicLab.Services.Hive.DataAccess.SlaveState" DbType="VarChar(15)" CanBeNull="false" />
+      <Column Name="IsAllowedToCalculate" Type="System.Boolean" DbType="BIT" CanBeNull="false" />
+      <Association Name="DimClient_FactClientInfo" Member="DimClient" ThisKey="ClientId" OtherKey="Id" Type="DimClient" IsForeignKey="true" />
+      <Association Name="DimTime_FactClientInfo" Member="DimTime" ThisKey="Time" OtherKey="Time" Type="DimTime" IsForeignKey="true" />
+      <Association Name="DimUser_FactClientInfo" Member="DimUser" ThisKey="UserId" OtherKey="UserId" Type="DimUser" IsForeignKey="true" />
+    </Type>
+  </Table>
 </Database>
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout	(revision 12878)
@@ -1,58 +1,58 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
-<ordesignerObjectsDiagram dslVersion="1.0.0.0" absoluteBounds="0, 0, 24.25, 8.5" name="HiveDataContext">
+<ordesignerObjectsDiagram dslVersion="1.0.0.0" absoluteBounds="0, 0, 15.75, 17.875" name="HiveDataContext">
   <DataContextMoniker Name="/HiveDataContext" />
   <nestedChildShapes>
-    <classShape Id="a929c9dc-69f4-4488-ba1c-a2342bf81d89" absoluteBounds="8.875, 4.5, 2, 1.0961531575520827">
+    <classShape Id="a929c9dc-69f4-4488-ba1c-a2342bf81d89" absoluteBounds="8.875, 4.5, 2, 1.1939925130208327">
       <DataClassMoniker Name="/HiveDataContext/AssignedResource" />
       <nestedChildShapes>
-        <elementListCompartment Id="8b005775-f0ee-41b0-ae10-6d1151003708" absoluteBounds="8.89, 4.9600000000000009, 1.9700000000000002, 0.53615315755208326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="7d998e56-4fba-41ca-a1a8-1dcdb9068edf" absoluteBounds="8.875, 5.875, 2, 1.7535424804687496">
+        <elementListCompartment Id="8b005775-f0ee-41b0-ae10-6d1151003708" absoluteBounds="8.89, 4.9600000000000009, 1.9700000000000002, 0.63399251302083326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="7d998e56-4fba-41ca-a1a8-1dcdb9068edf" absoluteBounds="8.875, 5.875, 2, 1.9631982421874996">
       <DataClassMoniker Name="/HiveDataContext/Plugin" />
       <nestedChildShapes>
-        <elementListCompartment Id="ec4ba325-6dff-4418-baad-59af81ae2024" absoluteBounds="8.89, 6.3350000000000009, 1.9700000000000002, 1.19354248046875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="97b00810-fa30-457e-b484-b4e80b22f91b" absoluteBounds="6.5, 5.875, 2, 1.2605004882812496">
+        <elementListCompartment Id="ec4ba325-6dff-4418-baad-59af81ae2024" absoluteBounds="8.89, 6.3350000000000009, 1.9700000000000002, 1.4031982421875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="97b00810-fa30-457e-b484-b4e80b22f91b" absoluteBounds="6.5, 5.875, 2, 1.3862939453124996">
       <DataClassMoniker Name="/HiveDataContext/RequiredPlugin" />
       <nestedChildShapes>
-        <elementListCompartment Id="df6451e5-069e-4ca0-a909-61b8213b5047" absoluteBounds="6.5150000000000006, 6.3350000000000009, 1.9700000000000002, 0.70050048828125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="706a4581-6daf-4e71-ae2a-87d50b27a051" absoluteBounds="11.25, 1, 2, 1.75354248046875">
+        <elementListCompartment Id="df6451e5-069e-4ca0-a909-61b8213b5047" absoluteBounds="6.5150000000000006, 6.3350000000000009, 1.9700000000000002, 0.8262939453125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="706a4581-6daf-4e71-ae2a-87d50b27a051" absoluteBounds="11.25, 1, 2, 1.9631982421875">
       <DataClassMoniker Name="/HiveDataContext/Resource" />
       <nestedChildShapes>
-        <elementListCompartment Id="8c24f5bf-2164-4d0f-832e-1730eb0066df" absoluteBounds="11.265, 1.46, 1.9700000000000002, 1.19354248046875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="695bfc39-59f3-4e60-8644-f847964bf62c" absoluteBounds="6.5, 1, 2, 2.9039737955729166">
+        <elementListCompartment Id="8c24f5bf-2164-4d0f-832e-1730eb0066df" absoluteBounds="11.265, 1.46, 1.9700000000000002, 1.4031982421875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="695bfc39-59f3-4e60-8644-f847964bf62c" absoluteBounds="6.5, 1, 2, 3.3093082682291666">
       <DataClassMoniker Name="/HiveDataContext/Task" />
       <nestedChildShapes>
-        <elementListCompartment Id="a6a30e11-03d1-4869-82e6-b733f4ef9974" absoluteBounds="6.5150000000000006, 1.46, 1.9700000000000002, 2.3439737955729165" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="8d5712f7-7a1a-4a89-bd4d-fd60200d3306" absoluteBounds="13.5, 2.5, 2, 2.0822371419270826">
+        <elementListCompartment Id="a6a30e11-03d1-4869-82e6-b733f4ef9974" absoluteBounds="6.5150000000000006, 1.46, 1.9700000000000002, 2.7493082682291665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="8d5712f7-7a1a-4a89-bd4d-fd60200d3306" absoluteBounds="13.5, 2.5, 2, 2.3478011067708326">
       <DataClassMoniker Name="/HiveDataContext/Downtime" />
       <nestedChildShapes>
-        <elementListCompartment Id="7d8f121b-35bb-4753-a25f-3fac1562e68e" absoluteBounds="13.515, 2.9600000000000009, 1.9700000000000002, 1.5222371419270833" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="e6f840cc-2968-4be1-b234-eef624ccacbb" absoluteBounds="4.125, 2.625, 2, 1.7535424804687496">
+        <elementListCompartment Id="7d8f121b-35bb-4753-a25f-3fac1562e68e" absoluteBounds="13.515, 2.9600000000000009, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="e6f840cc-2968-4be1-b234-eef624ccacbb" absoluteBounds="4.125, 2.625, 2, 1.9631982421874996">
       <DataClassMoniker Name="/HiveDataContext/Job" />
       <nestedChildShapes>
-        <elementListCompartment Id="0c65d4e1-256a-4a91-9a57-392f25e4de7f" absoluteBounds="4.1400000000000006, 3.0850000000000009, 1.9700000000000002, 1.19354248046875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="26f4edfa-91dd-4941-a058-359f89e567a8" absoluteBounds="8.875, 0.75, 2, 2.9039737955729166">
+        <elementListCompartment Id="0c65d4e1-256a-4a91-9a57-392f25e4de7f" absoluteBounds="4.1400000000000006, 3.0850000000000009, 1.9700000000000002, 1.4031982421875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="26f4edfa-91dd-4941-a058-359f89e567a8" absoluteBounds="8.875, 0.75, 2, 3.3093082682291666">
       <DataClassMoniker Name="/HiveDataContext/Slave" />
       <nestedChildShapes>
-        <elementListCompartment Id="1e61f36b-08dc-4df7-8594-c9dcd95c0791" absoluteBounds="8.89, 1.21, 1.9700000000000002, 2.3439737955729165" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="bbd91675-92f2-4a69-8429-0950008fc8a4" absoluteBounds="13.5, 1, 2, 0.76745849609375">
+        <elementListCompartment Id="1e61f36b-08dc-4df7-8594-c9dcd95c0791" absoluteBounds="8.89, 1.21, 1.9700000000000002, 2.7493082682291665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="bbd91675-92f2-4a69-8429-0950008fc8a4" absoluteBounds="13.5, 1, 2, 0.8093896484375">
       <DataClassMoniker Name="/HiveDataContext/SlaveGroup" />
       <nestedChildShapes>
-        <elementListCompartment Id="5a790e8f-6e2e-4bf5-bd2b-f8a82224d9d7" absoluteBounds="13.5, 1, 0.125, 0.20745849609375" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="5a790e8f-6e2e-4bf5-bd2b-f8a82224d9d7" absoluteBounds="13.5, 1, 0.125, 0.2493896484375" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -69,5 +69,5 @@
       </nodes>
     </inheritanceConnector>
-    <associationConnector edgePoints="[(11.8134963640734 : 2.75354248046875); (11.8134963640734 : 3.56770833333333); (11.1119791666667 : 3.56770833333333); (11.1119791666667 : 5.09699625651042); (10.875 : 5.09699625651042)]" manuallyRouted="true" fixedFrom="Algorithm" fixedTo="Algorithm">
+    <associationConnector edgePoints="[(11.8134963640734 : 2.9631982421875); (11.8134963640734 : 3.56770833333333); (11.1119791666667 : 3.56770833333333); (11.1119791666667 : 5.09699625651042); (10.875 : 5.09699625651042)]" manuallyRouted="true" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Resource/Resource_AssignedResource" />
       <nodes>
@@ -76,14 +76,14 @@
       </nodes>
     </associationConnector>
-    <classShape Id="6bc13f26-f9a8-4597-b054-35be34190d12" absoluteBounds="4.125, 1, 2, 1.26050048828125">
+    <classShape Id="6bc13f26-f9a8-4597-b054-35be34190d12" absoluteBounds="4.125, 1, 2, 1.3862939453125">
       <DataClassMoniker Name="/HiveDataContext/TaskData" />
       <nestedChildShapes>
-        <elementListCompartment Id="a068522c-7974-4679-b356-e33c941c465b" absoluteBounds="4.14, 1.46, 1.9700000000000002, 0.70050048828125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="ad25bd0f-80e8-4a06-abd8-190eb678eec7" absoluteBounds="11.25, 5.875, 2, 1.4248478190104166">
+        <elementListCompartment Id="a068522c-7974-4679-b356-e33c941c465b" absoluteBounds="4.14, 1.46, 1.9700000000000002, 0.8262939453125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="ad25bd0f-80e8-4a06-abd8-190eb678eec7" absoluteBounds="11.25, 5.875, 2, 1.5785953776041666">
       <DataClassMoniker Name="/HiveDataContext/PluginData" />
       <nestedChildShapes>
-        <elementListCompartment Id="acddb513-7de6-4bb4-8335-d6982fb2ef35" absoluteBounds="11.265, 6.3350000000000009, 1.9700000000000002, 0.86484781901041663" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="acddb513-7de6-4bb4-8335-d6982fb2ef35" absoluteBounds="11.265, 6.3350000000000009, 1.9700000000000002, 1.0185953776041665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -109,17 +109,17 @@
       </nodes>
     </associationConnector>
-    <classShape Id="00352397-340e-449a-8e23-6ddd216e8617" absoluteBounds="1.75, 1, 2, 1.9178898111979166">
+    <classShape Id="00352397-340e-449a-8e23-6ddd216e8617" absoluteBounds="1.75, 1, 2, 2.1554996744791666">
       <DataClassMoniker Name="/HiveDataContext/StateLog" />
       <nestedChildShapes>
-        <elementListCompartment Id="9a003897-deef-4bb5-b180-4c4bcdb7fadc" absoluteBounds="1.765, 1.46, 1.9700000000000002, 1.3578898111979165" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="4d800dc9-1b18-469f-b02c-d4554757c5e1" absoluteBounds="1.75, 3.625, 2, 1.4248478190104166">
+        <elementListCompartment Id="9a003897-deef-4bb5-b180-4c4bcdb7fadc" absoluteBounds="1.765, 1.46, 1.9700000000000002, 1.5954996744791665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="4d800dc9-1b18-469f-b02c-d4554757c5e1" absoluteBounds="1.75, 3.625, 2, 1.5785953776041666">
       <DataClassMoniker Name="/HiveDataContext/JobPermission" />
       <nestedChildShapes>
-        <elementListCompartment Id="dedd97d3-a9a2-45a2-9b95-d7366fb65a7f" absoluteBounds="1.765, 4.085, 1.9700000000000002, 0.86484781901041663" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <associationConnector edgePoints="[(11.25 : 1.98159912109375); (10.9375 : 1.98159912109375); (10.9375 : 0.6875); (2.75 : 0.6875); (2.75 : 1)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+        <elementListCompartment Id="dedd97d3-a9a2-45a2-9b95-d7366fb65a7f" absoluteBounds="1.765, 4.085, 1.9700000000000002, 1.0185953776041665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <associationConnector edgePoints="[(11.25 : 1.44547136382195); (11.0260416666667 : 1.44547136382195); (11.0260416666667 : 0.5625); (2.75 : 0.5625); (2.75 : 1)]" manuallyRouted="true" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Resource/Resource_StateLog" />
       <nodes>
@@ -128,48 +128,10 @@
       </nodes>
     </associationConnector>
-    <classShape Id="b4e7aaa8-a382-4560-a1fd-498de77e9340" absoluteBounds="1.75, 5.5, 2, 1.0961531575520827">
+    <classShape Id="b4e7aaa8-a382-4560-a1fd-498de77e9340" absoluteBounds="1.75, 5.5, 2, 1.1939925130208327">
       <DataClassMoniker Name="/HiveDataContext/Lifecycle" />
       <nestedChildShapes>
-        <elementListCompartment Id="03597a2c-5c5d-4c2c-b741-c49ab5aa34d5" absoluteBounds="1.7650000000000001, 5.96, 1.9700000000000002, 0.53615315755208326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="cdddf4da-eaef-46a1-9cfd-987bb6b3d03e" absoluteBounds="17, 3.125, 2.5, 1.5891951497395827">
-      <DataClassMoniker Name="/HiveDataContext/DeletedJobStatistics" />
-      <nestedChildShapes>
-        <elementListCompartment Id="b8738381-f696-4dba-a517-47e9cd96a9a6" absoluteBounds="17.015, 3.585, 2.4699999999999998, 1.0291951497395833" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="25442617-ff41-49a0-b5d5-4cbe0b2f0f45" absoluteBounds="22, 4.5, 2, 1.7535424804687496">
-      <DataClassMoniker Name="/HiveDataContext/UserStatistics" />
-      <nestedChildShapes>
-        <elementListCompartment Id="6edf08e7-054a-4253-8d4f-5bf2dfec2d29" absoluteBounds="22.015, 4.96, 1.9700000000000002, 1.19354248046875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="f78ec989-4862-4bac-87e3-9b0aad4bc037" absoluteBounds="19.875, 4.5, 2, 1.9178898111979166">
-      <DataClassMoniker Name="/HiveDataContext/SlaveStatistics" />
-      <nestedChildShapes>
-        <elementListCompartment Id="98cb46fe-820e-4041-aee8-04e8bed3df23" absoluteBounds="19.89, 4.96, 1.9700000000000002, 1.3578898111979165" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <classShape Id="4d9f0606-b7ee-4dae-a8ac-aff88d25a941" absoluteBounds="21, 3, 2, 1.0961531575520827">
-      <DataClassMoniker Name="/HiveDataContext/Statistics" />
-      <nestedChildShapes>
-        <elementListCompartment Id="17821282-8edf-40b7-9065-84f53391d5c1" absoluteBounds="21.015, 3.46, 1.9700000000000002, 0.53615315755208326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
-    <associationConnector edgePoints="[(21.4375 : 4.19399251302083); (21.4375 : 4.5)]" fixedFrom="NotFixed" fixedTo="NotFixed">
-      <AssociationMoniker Name="/HiveDataContext/Statistics/Statistics_SlaveStatistics" />
-      <nodes>
-        <classShapeMoniker Id="4d9f0606-b7ee-4dae-a8ac-aff88d25a941" />
-        <classShapeMoniker Id="f78ec989-4862-4bac-87e3-9b0aad4bc037" />
-      </nodes>
-    </associationConnector>
-    <associationConnector edgePoints="[(22.5 : 4.19399251302083); (22.5 : 4.5)]" fixedFrom="NotFixed" fixedTo="NotFixed">
-      <AssociationMoniker Name="/HiveDataContext/Statistics/Statistics_UserStatistics" />
-      <nodes>
-        <classShapeMoniker Id="4d9f0606-b7ee-4dae-a8ac-aff88d25a941" />
-        <classShapeMoniker Id="25442617-ff41-49a0-b5d5-4cbe0b2f0f45" />
-      </nodes>
-    </associationConnector>
+        <elementListCompartment Id="03597a2c-5c5d-4c2c-b741-c49ab5aa34d5" absoluteBounds="1.7650000000000001, 5.96, 1.9700000000000002, 0.63399251302083326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
     <associationConnector edgePoints="[(13.25 : 2.73159912109375); (13.5 : 2.73159912109375)]" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Resource/Resource_Downtime" />
@@ -179,5 +141,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(7.96163946463664 : 4.30930826822917); (7.96163946463664 : 5.09699625651042); (8.875 : 5.09699625651042)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(8.0192961430407 : 3.90397379557292); (8.0192961430407 : 5.04807657877604); (8.875 : 5.04807657877604)]" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Task/Task_AssignedResource" />
       <nodes>
@@ -186,5 +148,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(7.16831973231832 : 4.30930826822917); (7.16831973231832 : 5.875)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(7.20145120722822 : 3.90397379557292); (7.20145120722822 : 5.875)]" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Task/Task_RequiredPlugin" />
       <nodes>
@@ -193,5 +155,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(7.20923076923077 : 1); (7.20923076923077 : 0.875); (7.80461538461539 : 0.875); (7.80461538461539 : 1)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(7.20923076923077 : 1); (7.20923076923077 : 0.770833333333333); (7.80461538461539 : 0.770833333333333); (7.80461538461539 : 1)]" manuallyRouted="true" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/Task/Task_Task" />
       <nodes>
@@ -228,8 +190,8 @@
       </nodes>
     </associationConnector>
-    <classShape Id="a3f352be-9f15-4e73-8d44-3e8ac02fb4cf" absoluteBounds="11.25, 3.875, 2, 1.2605004882812496">
+    <classShape Id="a3f352be-9f15-4e73-8d44-3e8ac02fb4cf" absoluteBounds="11.25, 3.875, 2, 1.3862939453124996">
       <DataClassMoniker Name="/HiveDataContext/ResourcePermission" />
       <nestedChildShapes>
-        <elementListCompartment Id="45e2f1a8-8a1e-4647-b649-10ec55976ab4" absoluteBounds="11.265, 4.335, 1.9700000000000002, 0.70050048828125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="45e2f1a8-8a1e-4647-b649-10ec55976ab4" absoluteBounds="11.265, 4.335, 1.9700000000000002, 0.8262939453125" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -241,10 +203,81 @@
       </nodes>
     </associationConnector>
-    <classShape Id="f9e8867f-fd15-4a72-8ca4-4f02cd3f141f" absoluteBounds="4.125, 5.5, 2, 1.0961531575520827">
+    <classShape Id="f9e8867f-fd15-4a72-8ca4-4f02cd3f141f" absoluteBounds="4.125, 5.5, 2, 1.1939925130208327">
       <DataClassMoniker Name="/HiveDataContext/UserPriority" />
       <nestedChildShapes>
-        <elementListCompartment Id="ee41f516-7d9c-4a1d-a1b8-bbe00a6ffea8" absoluteBounds="4.14, 5.96, 1.9700000000000002, 0.53615315755208326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
-      </nestedChildShapes>
-    </classShape>
+        <elementListCompartment Id="ee41f516-7d9c-4a1d-a1b8-bbe00a6ffea8" absoluteBounds="4.14, 5.96, 1.9700000000000002, 0.63399251302083326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="b5b919c2-4efc-4f09-8f52-9d541a11e961" absoluteBounds="4.625, 15.25, 2, 2.3478011067708344">
+      <DataClassMoniker Name="/HiveDataContext/DimClient" />
+      <nestedChildShapes>
+        <elementListCompartment Id="30f62a7b-0b16-404e-b972-fb12bfe978dd" absoluteBounds="4.6400000000000006, 15.71, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="04f52807-ce17-4d65-bd23-cc38c6dfbd7a" absoluteBounds="6.875, 11.25, 2, 3.8862125651041666">
+      <DataClassMoniker Name="/HiveDataContext/FactTask" />
+      <nestedChildShapes>
+        <elementListCompartment Id="63e3ddcb-a6fe-48e0-a674-e650a55a9f2c" absoluteBounds="6.8900000000000006, 11.71, 1.9700000000000002, 3.3262125651041665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="e0cb8641-a75e-4b9f-beda-3218c56938b1" absoluteBounds="8.125, 8.25, 2, 2.3478011067708344">
+      <DataClassMoniker Name="/HiveDataContext/DimJob" />
+      <nestedChildShapes>
+        <elementListCompartment Id="6b9e8260-7e4b-4357-9c26-eebebfd69504" absoluteBounds="8.14, 8.71, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="a769f2ac-c8e3-4860-baa5-c46f46c38ed8" absoluteBounds="3.625, 8.5, 2, 1.9631982421875005">
+      <DataClassMoniker Name="/HiveDataContext/DimTime" />
+      <nestedChildShapes>
+        <elementListCompartment Id="694c4a5a-20fb-43a9-bc9a-7b59d794a7fb" absoluteBounds="3.6399999999999997, 8.96, 1.9700000000000002, 1.4031982421875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="66c34ced-4fdb-4a1d-b8dd-fe094679b7fb" absoluteBounds="0.625, 8.875, 2, 1.1939925130208344">
+      <DataClassMoniker Name="/HiveDataContext/DimUser" />
+      <nestedChildShapes>
+        <elementListCompartment Id="6181c560-fc74-4d43-b064-a90032bf17a5" absoluteBounds="0.64000000000000012, 9.335, 1.9700000000000002, 0.63399251302083326" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <classShape Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" absoluteBounds="2.625, 11, 2, 3.3093082682291666">
+      <DataClassMoniker Name="/HiveDataContext/FactClientInfo" />
+      <nestedChildShapes>
+        <elementListCompartment Id="0a1cc913-6636-455c-95b3-302f533527db" absoluteBounds="2.6399999999999997, 11.46, 1.9700000000000002, 2.7493082682291665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+      </nestedChildShapes>
+    </classShape>
+    <associationConnector edgePoints="[(6.625 : 15.25); (6.875 : 15.1362125651042)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+      <AssociationMoniker Name="/HiveDataContext/DimClient/DimClient_FactTask" />
+      <nodes>
+        <classShapeMoniker Id="b5b919c2-4efc-4f09-8f52-9d541a11e961" />
+        <classShapeMoniker Id="04f52807-ce17-4d65-bd23-cc38c6dfbd7a" />
+      </nodes>
+    </associationConnector>
+    <associationConnector edgePoints="[(8.5 : 10.5978011067708); (8.5 : 11.25)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+      <AssociationMoniker Name="/HiveDataContext/DimJob/DimJob_FactTask" />
+      <nodes>
+        <classShapeMoniker Id="e0cb8641-a75e-4b9f-beda-3218c56938b1" />
+        <classShapeMoniker Id="04f52807-ce17-4d65-bd23-cc38c6dfbd7a" />
+      </nodes>
+    </associationConnector>
+    <associationConnector edgePoints="[(5.6249975 : 15.25); (5.6249975 : 12.6546541341146); (4.625 : 12.6546541341146)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+      <AssociationMoniker Name="/HiveDataContext/DimClient/DimClient_FactClientInfo" />
+      <nodes>
+        <classShapeMoniker Id="b5b919c2-4efc-4f09-8f52-9d541a11e961" />
+        <classShapeMoniker Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" />
+      </nodes>
+    </associationConnector>
+    <associationConnector edgePoints="[(4.125 : 10.4631982421875); (4.125 : 11)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+      <AssociationMoniker Name="/HiveDataContext/DimTime/DimTime_FactClientInfo" />
+      <nodes>
+        <classShapeMoniker Id="a769f2ac-c8e3-4860-baa5-c46f46c38ed8" />
+        <classShapeMoniker Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" />
+      </nodes>
+    </associationConnector>
+    <associationConnector edgePoints="[(1.59375 : 10.0689925130208); (1.59375 : 12.6546541341146); (2.625 : 12.6546541341146)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+      <AssociationMoniker Name="/HiveDataContext/DimUser/DimUser_FactClientInfo" />
+      <nodes>
+        <classShapeMoniker Id="66c34ced-4fdb-4a1d-b8dd-fe094679b7fb" />
+        <classShapeMoniker Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" />
+      </nodes>
+    </associationConnector>
   </nestedChildShapes>
 </ordesignerObjectsDiagram>
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs	(revision 12878)
@@ -3,5 +3,5 @@
 // <auto-generated>
 //     This code was generated by a tool.
-//     Runtime Version:4.0.30319.34014
+//     Runtime Version:4.0.30319.34209
 //
 //     Changes to this file may cause incorrect behavior and will be lost if
@@ -67,16 +67,4 @@
     partial void UpdateLifecycle(Lifecycle instance);
     partial void DeleteLifecycle(Lifecycle instance);
-    partial void InsertDeletedJobStatistics(DeletedJobStatistics instance);
-    partial void UpdateDeletedJobStatistics(DeletedJobStatistics instance);
-    partial void DeleteDeletedJobStatistics(DeletedJobStatistics instance);
-    partial void InsertUserStatistics(UserStatistics instance);
-    partial void UpdateUserStatistics(UserStatistics instance);
-    partial void DeleteUserStatistics(UserStatistics instance);
-    partial void InsertSlaveStatistics(SlaveStatistics instance);
-    partial void UpdateSlaveStatistics(SlaveStatistics instance);
-    partial void DeleteSlaveStatistics(SlaveStatistics instance);
-    partial void InsertStatistics(Statistics instance);
-    partial void UpdateStatistics(Statistics instance);
-    partial void DeleteStatistics(Statistics instance);
     partial void InsertResourcePermission(ResourcePermission instance);
     partial void UpdateResourcePermission(ResourcePermission instance);
@@ -85,4 +73,22 @@
     partial void UpdateUserPriority(UserPriority instance);
     partial void DeleteUserPriority(UserPriority instance);
+    partial void InsertDimClient(DimClient instance);
+    partial void UpdateDimClient(DimClient instance);
+    partial void DeleteDimClient(DimClient instance);
+    partial void InsertFactTask(FactTask instance);
+    partial void UpdateFactTask(FactTask instance);
+    partial void DeleteFactTask(FactTask instance);
+    partial void InsertDimJob(DimJob instance);
+    partial void UpdateDimJob(DimJob instance);
+    partial void DeleteDimJob(DimJob instance);
+    partial void InsertDimTime(DimTime instance);
+    partial void UpdateDimTime(DimTime instance);
+    partial void DeleteDimTime(DimTime instance);
+    partial void InsertDimUser(DimUser instance);
+    partial void UpdateDimUser(DimUser instance);
+    partial void DeleteDimUser(DimUser instance);
+    partial void InsertFactClientInfo(FactClientInfo instance);
+    partial void UpdateFactClientInfo(FactClientInfo instance);
+    partial void DeleteFactClientInfo(FactClientInfo instance);
     #endregion
 		
@@ -213,36 +219,4 @@
 		}
 		
-		public System.Data.Linq.Table<DeletedJobStatistics> DeletedJobStatistics
-		{
-			get
-			{
-				return this.GetTable<DeletedJobStatistics>();
-			}
-		}
-		
-		public System.Data.Linq.Table<UserStatistics> UserStatistics
-		{
-			get
-			{
-				return this.GetTable<UserStatistics>();
-			}
-		}
-		
-		public System.Data.Linq.Table<SlaveStatistics> SlaveStatistics
-		{
-			get
-			{
-				return this.GetTable<SlaveStatistics>();
-			}
-		}
-		
-		public System.Data.Linq.Table<Statistics> Statistics
-		{
-			get
-			{
-				return this.GetTable<Statistics>();
-			}
-		}
-		
 		public System.Data.Linq.Table<ResourcePermission> ResourcePermissions
 		{
@@ -258,4 +232,52 @@
 			{
 				return this.GetTable<UserPriority>();
+			}
+		}
+		
+		public System.Data.Linq.Table<DimClient> DimClients
+		{
+			get
+			{
+				return this.GetTable<DimClient>();
+			}
+		}
+		
+		public System.Data.Linq.Table<FactTask> FactTasks
+		{
+			get
+			{
+				return this.GetTable<FactTask>();
+			}
+		}
+		
+		public System.Data.Linq.Table<DimJob> DimJobs
+		{
+			get
+			{
+				return this.GetTable<DimJob>();
+			}
+		}
+		
+		public System.Data.Linq.Table<DimTime> DimTimes
+		{
+			get
+			{
+				return this.GetTable<DimTime>();
+			}
+		}
+		
+		public System.Data.Linq.Table<DimUser> DimUsers
+		{
+			get
+			{
+				return this.GetTable<DimUser>();
+			}
+		}
+		
+		public System.Data.Linq.Table<FactClientInfo> FactClientInfos
+		{
+			get
+			{
+				return this.GetTable<FactClientInfo>();
 			}
 		}
@@ -3528,774 +3550,4 @@
 	}
 	
-	[global::System.Data.Linq.Mapping.TableAttribute(Name="")]
-	public partial class DeletedJobStatistics : INotifyPropertyChanging, INotifyPropertyChanged
-	{
-		
-		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
-		
-		private System.Guid _UserId;
-		
-		private double _ExecutionTime;
-		
-		private double _ExecutionTimeFinishedJobs;
-		
-		private double _StartToEndTime;
-		
-		private System.Guid _DeletedJobStatisticsId;
-		
-    #region Extensibility Method Definitions
-    partial void OnLoaded();
-    partial void OnValidate(System.Data.Linq.ChangeAction action);
-    partial void OnCreated();
-    partial void OnUserIdChanging(System.Guid value);
-    partial void OnUserIdChanged();
-    partial void OnExecutionTimeSChanging(double value);
-    partial void OnExecutionTimeSChanged();
-    partial void OnExecutionTimeSFinishedJobsChanging(double value);
-    partial void OnExecutionTimeSFinishedJobsChanged();
-    partial void OnStartToEndTimeSChanging(double value);
-    partial void OnStartToEndTimeSChanged();
-    partial void OnDeletedJobStatisticsIdChanging(System.Guid value);
-    partial void OnDeletedJobStatisticsIdChanged();
-    #endregion
-		
-		public DeletedJobStatistics()
-		{
-			OnCreated();
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserId", DbType="UniqueIdentifier NOT NULL")]
-		public System.Guid UserId
-		{
-			get
-			{
-				return this._UserId;
-			}
-			set
-			{
-				if ((this._UserId != value))
-				{
-					this.OnUserIdChanging(value);
-					this.SendPropertyChanging();
-					this._UserId = value;
-					this.SendPropertyChanged("UserId");
-					this.OnUserIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExecutionTime", DbType="float NOT NULL")]
-		public double ExecutionTimeS
-		{
-			get
-			{
-				return this._ExecutionTime;
-			}
-			set
-			{
-				if ((this._ExecutionTime != value))
-				{
-					this.OnExecutionTimeSChanging(value);
-					this.SendPropertyChanging();
-					this._ExecutionTime = value;
-					this.SendPropertyChanged("ExecutionTimeS");
-					this.OnExecutionTimeSChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExecutionTimeFinishedJobs", DbType="float NOT NULL")]
-		public double ExecutionTimeSFinishedJobs
-		{
-			get
-			{
-				return this._ExecutionTimeFinishedJobs;
-			}
-			set
-			{
-				if ((this._ExecutionTimeFinishedJobs != value))
-				{
-					this.OnExecutionTimeSFinishedJobsChanging(value);
-					this.SendPropertyChanging();
-					this._ExecutionTimeFinishedJobs = value;
-					this.SendPropertyChanged("ExecutionTimeSFinishedJobs");
-					this.OnExecutionTimeSFinishedJobsChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StartToEndTime", DbType="float NOT NULL")]
-		public double StartToEndTimeS
-		{
-			get
-			{
-				return this._StartToEndTime;
-			}
-			set
-			{
-				if ((this._StartToEndTime != value))
-				{
-					this.OnStartToEndTimeSChanging(value);
-					this.SendPropertyChanging();
-					this._StartToEndTime = value;
-					this.SendPropertyChanged("StartToEndTimeS");
-					this.OnStartToEndTimeSChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DeletedJobStatisticsId", AutoSync=AutoSync.OnInsert, DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
-		public System.Guid DeletedJobStatisticsId
-		{
-			get
-			{
-				return this._DeletedJobStatisticsId;
-			}
-			set
-			{
-				if ((this._DeletedJobStatisticsId != value))
-				{
-					this.OnDeletedJobStatisticsIdChanging(value);
-					this.SendPropertyChanging();
-					this._DeletedJobStatisticsId = value;
-					this.SendPropertyChanged("DeletedJobStatisticsId");
-					this.OnDeletedJobStatisticsIdChanged();
-				}
-			}
-		}
-		
-		public event PropertyChangingEventHandler PropertyChanging;
-		
-		public event PropertyChangedEventHandler PropertyChanged;
-		
-		protected virtual void SendPropertyChanging()
-		{
-			if ((this.PropertyChanging != null))
-			{
-				this.PropertyChanging(this, emptyChangingEventArgs);
-			}
-		}
-		
-		protected virtual void SendPropertyChanged(String propertyName)
-		{
-			if ((this.PropertyChanged != null))
-			{
-				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
-			}
-		}
-	}
-	
-	[global::System.Data.Linq.Mapping.TableAttribute(Name="")]
-	public partial class UserStatistics : INotifyPropertyChanging, INotifyPropertyChanged
-	{
-		
-		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
-		
-		private System.Guid _StatisticsId;
-		
-		private System.Guid _UserId;
-		
-		private double _ExecutionTime;
-		
-		private int _CoresUsed;
-		
-		private double _ExecutionTimeFinishedJobs;
-		
-		private double _StartToEndTime;
-		
-		private EntityRef<Statistics> _Statistics;
-		
-    #region Extensibility Method Definitions
-    partial void OnLoaded();
-    partial void OnValidate(System.Data.Linq.ChangeAction action);
-    partial void OnCreated();
-    partial void OnStatisticsIdChanging(System.Guid value);
-    partial void OnStatisticsIdChanged();
-    partial void OnUserIdChanging(System.Guid value);
-    partial void OnUserIdChanged();
-    partial void OnExecutionTimeMsChanging(double value);
-    partial void OnExecutionTimeMsChanged();
-    partial void OnUsedCoresChanging(int value);
-    partial void OnUsedCoresChanged();
-    partial void OnExecutionTimeMsFinishedJobsChanging(double value);
-    partial void OnExecutionTimeMsFinishedJobsChanged();
-    partial void OnStartToEndTimeMsChanging(double value);
-    partial void OnStartToEndTimeMsChanged();
-    #endregion
-		
-		public UserStatistics()
-		{
-			this._Statistics = default(EntityRef<Statistics>);
-			OnCreated();
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StatisticsId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
-		public System.Guid StatisticsId
-		{
-			get
-			{
-				return this._StatisticsId;
-			}
-			set
-			{
-				if ((this._StatisticsId != value))
-				{
-					if (this._Statistics.HasLoadedOrAssignedValue)
-					{
-						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
-					}
-					this.OnStatisticsIdChanging(value);
-					this.SendPropertyChanging();
-					this._StatisticsId = value;
-					this.SendPropertyChanged("StatisticsId");
-					this.OnStatisticsIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
-		public System.Guid UserId
-		{
-			get
-			{
-				return this._UserId;
-			}
-			set
-			{
-				if ((this._UserId != value))
-				{
-					this.OnUserIdChanging(value);
-					this.SendPropertyChanging();
-					this._UserId = value;
-					this.SendPropertyChanged("UserId");
-					this.OnUserIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExecutionTime", DbType="float NOT NULL")]
-		public double ExecutionTimeMs
-		{
-			get
-			{
-				return this._ExecutionTime;
-			}
-			set
-			{
-				if ((this._ExecutionTime != value))
-				{
-					this.OnExecutionTimeMsChanging(value);
-					this.SendPropertyChanging();
-					this._ExecutionTime = value;
-					this.SendPropertyChanged("ExecutionTimeMs");
-					this.OnExecutionTimeMsChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CoresUsed", DbType="Int NOT NULL")]
-		public int UsedCores
-		{
-			get
-			{
-				return this._CoresUsed;
-			}
-			set
-			{
-				if ((this._CoresUsed != value))
-				{
-					this.OnUsedCoresChanging(value);
-					this.SendPropertyChanging();
-					this._CoresUsed = value;
-					this.SendPropertyChanged("UsedCores");
-					this.OnUsedCoresChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExecutionTimeFinishedJobs", DbType="float NOT NULL")]
-		public double ExecutionTimeMsFinishedJobs
-		{
-			get
-			{
-				return this._ExecutionTimeFinishedJobs;
-			}
-			set
-			{
-				if ((this._ExecutionTimeFinishedJobs != value))
-				{
-					this.OnExecutionTimeMsFinishedJobsChanging(value);
-					this.SendPropertyChanging();
-					this._ExecutionTimeFinishedJobs = value;
-					this.SendPropertyChanged("ExecutionTimeMsFinishedJobs");
-					this.OnExecutionTimeMsFinishedJobsChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StartToEndTime", DbType="float NOT NULL")]
-		public double StartToEndTimeMs
-		{
-			get
-			{
-				return this._StartToEndTime;
-			}
-			set
-			{
-				if ((this._StartToEndTime != value))
-				{
-					this.OnStartToEndTimeMsChanging(value);
-					this.SendPropertyChanging();
-					this._StartToEndTime = value;
-					this.SendPropertyChanged("StartToEndTimeMs");
-					this.OnStartToEndTimeMsChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Statistics_UserStatistics", Storage="_Statistics", ThisKey="StatisticsId", OtherKey="StatisticsId", IsForeignKey=true)]
-		public Statistics Statistics
-		{
-			get
-			{
-				return this._Statistics.Entity;
-			}
-			set
-			{
-				Statistics previousValue = this._Statistics.Entity;
-				if (((previousValue != value) 
-							|| (this._Statistics.HasLoadedOrAssignedValue == false)))
-				{
-					this.SendPropertyChanging();
-					if ((previousValue != null))
-					{
-						this._Statistics.Entity = null;
-						previousValue.UserStatistics.Remove(this);
-					}
-					this._Statistics.Entity = value;
-					if ((value != null))
-					{
-						value.UserStatistics.Add(this);
-						this._StatisticsId = value.StatisticsId;
-					}
-					else
-					{
-						this._StatisticsId = default(System.Guid);
-					}
-					this.SendPropertyChanged("Statistics");
-				}
-			}
-		}
-		
-		public event PropertyChangingEventHandler PropertyChanging;
-		
-		public event PropertyChangedEventHandler PropertyChanged;
-		
-		protected virtual void SendPropertyChanging()
-		{
-			if ((this.PropertyChanging != null))
-			{
-				this.PropertyChanging(this, emptyChangingEventArgs);
-			}
-		}
-		
-		protected virtual void SendPropertyChanged(String propertyName)
-		{
-			if ((this.PropertyChanged != null))
-			{
-				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
-			}
-		}
-	}
-	
-	[global::System.Data.Linq.Mapping.TableAttribute(Name="")]
-	public partial class SlaveStatistics : INotifyPropertyChanging, INotifyPropertyChanged
-	{
-		
-		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
-		
-		private System.Guid _StatisticsId;
-		
-		private System.Guid _SlaveId;
-		
-		private int _Cores;
-		
-		private int _FreeCores;
-		
-		private double _CpuUtilization;
-		
-		private int _Memory;
-		
-		private int _FreeMemory;
-		
-		private EntityRef<Statistics> _Statistics;
-		
-    #region Extensibility Method Definitions
-    partial void OnLoaded();
-    partial void OnValidate(System.Data.Linq.ChangeAction action);
-    partial void OnCreated();
-    partial void OnStatisticsIdChanging(System.Guid value);
-    partial void OnStatisticsIdChanged();
-    partial void OnSlaveIdChanging(System.Guid value);
-    partial void OnSlaveIdChanged();
-    partial void OnCoresChanging(int value);
-    partial void OnCoresChanged();
-    partial void OnFreeCoresChanging(int value);
-    partial void OnFreeCoresChanged();
-    partial void OnCpuUtilizationChanging(double value);
-    partial void OnCpuUtilizationChanged();
-    partial void OnMemoryChanging(int value);
-    partial void OnMemoryChanged();
-    partial void OnFreeMemoryChanging(int value);
-    partial void OnFreeMemoryChanged();
-    #endregion
-		
-		public SlaveStatistics()
-		{
-			this._Statistics = default(EntityRef<Statistics>);
-			OnCreated();
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StatisticsId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
-		public System.Guid StatisticsId
-		{
-			get
-			{
-				return this._StatisticsId;
-			}
-			set
-			{
-				if ((this._StatisticsId != value))
-				{
-					if (this._Statistics.HasLoadedOrAssignedValue)
-					{
-						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
-					}
-					this.OnStatisticsIdChanging(value);
-					this.SendPropertyChanging();
-					this._StatisticsId = value;
-					this.SendPropertyChanged("StatisticsId");
-					this.OnStatisticsIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_SlaveId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
-		public System.Guid SlaveId
-		{
-			get
-			{
-				return this._SlaveId;
-			}
-			set
-			{
-				if ((this._SlaveId != value))
-				{
-					this.OnSlaveIdChanging(value);
-					this.SendPropertyChanging();
-					this._SlaveId = value;
-					this.SendPropertyChanged("SlaveId");
-					this.OnSlaveIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Cores", DbType="Int NOT NULL")]
-		public int Cores
-		{
-			get
-			{
-				return this._Cores;
-			}
-			set
-			{
-				if ((this._Cores != value))
-				{
-					this.OnCoresChanging(value);
-					this.SendPropertyChanging();
-					this._Cores = value;
-					this.SendPropertyChanged("Cores");
-					this.OnCoresChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_FreeCores", DbType="Int NOT NULL")]
-		public int FreeCores
-		{
-			get
-			{
-				return this._FreeCores;
-			}
-			set
-			{
-				if ((this._FreeCores != value))
-				{
-					this.OnFreeCoresChanging(value);
-					this.SendPropertyChanging();
-					this._FreeCores = value;
-					this.SendPropertyChanged("FreeCores");
-					this.OnFreeCoresChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CpuUtilization", DbType="float NOT NULL")]
-		public double CpuUtilization
-		{
-			get
-			{
-				return this._CpuUtilization;
-			}
-			set
-			{
-				if ((this._CpuUtilization != value))
-				{
-					this.OnCpuUtilizationChanging(value);
-					this.SendPropertyChanging();
-					this._CpuUtilization = value;
-					this.SendPropertyChanged("CpuUtilization");
-					this.OnCpuUtilizationChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Memory", DbType="Int NOT NULL")]
-		public int Memory
-		{
-			get
-			{
-				return this._Memory;
-			}
-			set
-			{
-				if ((this._Memory != value))
-				{
-					this.OnMemoryChanging(value);
-					this.SendPropertyChanging();
-					this._Memory = value;
-					this.SendPropertyChanged("Memory");
-					this.OnMemoryChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_FreeMemory", DbType="Int NOT NULL")]
-		public int FreeMemory
-		{
-			get
-			{
-				return this._FreeMemory;
-			}
-			set
-			{
-				if ((this._FreeMemory != value))
-				{
-					this.OnFreeMemoryChanging(value);
-					this.SendPropertyChanging();
-					this._FreeMemory = value;
-					this.SendPropertyChanged("FreeMemory");
-					this.OnFreeMemoryChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Statistics_SlaveStatistics", Storage="_Statistics", ThisKey="StatisticsId", OtherKey="StatisticsId", IsForeignKey=true)]
-		public Statistics Statistics
-		{
-			get
-			{
-				return this._Statistics.Entity;
-			}
-			set
-			{
-				Statistics previousValue = this._Statistics.Entity;
-				if (((previousValue != value) 
-							|| (this._Statistics.HasLoadedOrAssignedValue == false)))
-				{
-					this.SendPropertyChanging();
-					if ((previousValue != null))
-					{
-						this._Statistics.Entity = null;
-						previousValue.SlaveStatistics.Remove(this);
-					}
-					this._Statistics.Entity = value;
-					if ((value != null))
-					{
-						value.SlaveStatistics.Add(this);
-						this._StatisticsId = value.StatisticsId;
-					}
-					else
-					{
-						this._StatisticsId = default(System.Guid);
-					}
-					this.SendPropertyChanged("Statistics");
-				}
-			}
-		}
-		
-		public event PropertyChangingEventHandler PropertyChanging;
-		
-		public event PropertyChangedEventHandler PropertyChanged;
-		
-		protected virtual void SendPropertyChanging()
-		{
-			if ((this.PropertyChanging != null))
-			{
-				this.PropertyChanging(this, emptyChangingEventArgs);
-			}
-		}
-		
-		protected virtual void SendPropertyChanged(String propertyName)
-		{
-			if ((this.PropertyChanged != null))
-			{
-				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
-			}
-		}
-	}
-	
-	[global::System.Data.Linq.Mapping.TableAttribute(Name="")]
-	public partial class Statistics : INotifyPropertyChanging, INotifyPropertyChanged
-	{
-		
-		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
-		
-		private System.Guid _StatisticsId;
-		
-		private System.DateTime _Timestamp;
-		
-		private EntitySet<UserStatistics> _UserStatistics;
-		
-		private EntitySet<SlaveStatistics> _SlaveStatistics;
-		
-    #region Extensibility Method Definitions
-    partial void OnLoaded();
-    partial void OnValidate(System.Data.Linq.ChangeAction action);
-    partial void OnCreated();
-    partial void OnStatisticsIdChanging(System.Guid value);
-    partial void OnStatisticsIdChanged();
-    partial void OnTimestampChanging(System.DateTime value);
-    partial void OnTimestampChanged();
-    #endregion
-		
-		public Statistics()
-		{
-			this._UserStatistics = new EntitySet<UserStatistics>(new Action<UserStatistics>(this.attach_UserStatistics), new Action<UserStatistics>(this.detach_UserStatistics));
-			this._SlaveStatistics = new EntitySet<SlaveStatistics>(new Action<SlaveStatistics>(this.attach_SlaveStatistics), new Action<SlaveStatistics>(this.detach_SlaveStatistics));
-			OnCreated();
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StatisticsId", AutoSync=AutoSync.OnInsert, DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
-		public System.Guid StatisticsId
-		{
-			get
-			{
-				return this._StatisticsId;
-			}
-			set
-			{
-				if ((this._StatisticsId != value))
-				{
-					this.OnStatisticsIdChanging(value);
-					this.SendPropertyChanging();
-					this._StatisticsId = value;
-					this.SendPropertyChanged("StatisticsId");
-					this.OnStatisticsIdChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Timestamp")]
-		public System.DateTime Timestamp
-		{
-			get
-			{
-				return this._Timestamp;
-			}
-			set
-			{
-				if ((this._Timestamp != value))
-				{
-					this.OnTimestampChanging(value);
-					this.SendPropertyChanging();
-					this._Timestamp = value;
-					this.SendPropertyChanged("Timestamp");
-					this.OnTimestampChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Statistics_UserStatistics", Storage="_UserStatistics", ThisKey="StatisticsId", OtherKey="StatisticsId")]
-		public EntitySet<UserStatistics> UserStatistics
-		{
-			get
-			{
-				return this._UserStatistics;
-			}
-			set
-			{
-				this._UserStatistics.Assign(value);
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Statistics_SlaveStatistics", Storage="_SlaveStatistics", ThisKey="StatisticsId", OtherKey="StatisticsId")]
-		public EntitySet<SlaveStatistics> SlaveStatistics
-		{
-			get
-			{
-				return this._SlaveStatistics;
-			}
-			set
-			{
-				this._SlaveStatistics.Assign(value);
-			}
-		}
-		
-		public event PropertyChangingEventHandler PropertyChanging;
-		
-		public event PropertyChangedEventHandler PropertyChanged;
-		
-		protected virtual void SendPropertyChanging()
-		{
-			if ((this.PropertyChanging != null))
-			{
-				this.PropertyChanging(this, emptyChangingEventArgs);
-			}
-		}
-		
-		protected virtual void SendPropertyChanged(String propertyName)
-		{
-			if ((this.PropertyChanged != null))
-			{
-				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
-			}
-		}
-		
-		private void attach_UserStatistics(UserStatistics entity)
-		{
-			this.SendPropertyChanging();
-			entity.Statistics = this;
-		}
-		
-		private void detach_UserStatistics(UserStatistics entity)
-		{
-			this.SendPropertyChanging();
-			entity.Statistics = null;
-		}
-		
-		private void attach_SlaveStatistics(SlaveStatistics entity)
-		{
-			this.SendPropertyChanging();
-			entity.Statistics = this;
-		}
-		
-		private void detach_SlaveStatistics(SlaveStatistics entity)
-		{
-			this.SendPropertyChanging();
-			entity.Statistics = null;
-		}
-	}
-	
 	[global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.ResourcePermission")]
 	public partial class ResourcePermission : INotifyPropertyChanging, INotifyPropertyChanged
@@ -4534,4 +3786,1849 @@
 		}
 	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].DimClient")]
+	public partial class DimClient : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.Guid _Id;
+		
+		private string _Name;
+		
+		private System.Guid _ResourceId;
+		
+		private System.Nullable<System.DateTime> _ExpirationTime;
+		
+		private System.Nullable<System.Guid> _ResourceGroupId;
+		
+		private System.Nullable<System.Guid> _ResourceGroup2Id;
+		
+		private string _GroupName;
+		
+		private string _GroupName2;
+		
+		private EntitySet<FactTask> _FactTasks;
+		
+		private EntitySet<FactClientInfo> _FactClientInfos;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnIdChanging(System.Guid value);
+    partial void OnIdChanged();
+    partial void OnNameChanging(string value);
+    partial void OnNameChanged();
+    partial void OnResourceIdChanging(System.Guid value);
+    partial void OnResourceIdChanged();
+    partial void OnExpirationTimeChanging(System.Nullable<System.DateTime> value);
+    partial void OnExpirationTimeChanged();
+    partial void OnResourceGroupIdChanging(System.Nullable<System.Guid> value);
+    partial void OnResourceGroupIdChanged();
+    partial void OnResourceGroup2IdChanging(System.Nullable<System.Guid> value);
+    partial void OnResourceGroup2IdChanged();
+    partial void OnGroupNameChanging(string value);
+    partial void OnGroupNameChanged();
+    partial void OnGroupName2Changing(string value);
+    partial void OnGroupName2Changed();
+    #endregion
+		
+		public DimClient()
+		{
+			this._FactTasks = new EntitySet<FactTask>(new Action<FactTask>(this.attach_FactTasks), new Action<FactTask>(this.detach_FactTasks));
+			this._FactClientInfos = new EntitySet<FactClientInfo>(new Action<FactClientInfo>(this.attach_FactClientInfos), new Action<FactClientInfo>(this.detach_FactClientInfos));
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Id", AutoSync=AutoSync.OnInsert, DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
+		public System.Guid Id
+		{
+			get
+			{
+				return this._Id;
+			}
+			set
+			{
+				if ((this._Id != value))
+				{
+					this.OnIdChanging(value);
+					this.SendPropertyChanging();
+					this._Id = value;
+					this.SendPropertyChanged("Id");
+					this.OnIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Name", DbType="VarChar(MAX) NOT NULL", CanBeNull=false)]
+		public string Name
+		{
+			get
+			{
+				return this._Name;
+			}
+			set
+			{
+				if ((this._Name != value))
+				{
+					this.OnNameChanging(value);
+					this.SendPropertyChanging();
+					this._Name = value;
+					this.SendPropertyChanged("Name");
+					this.OnNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ResourceId", DbType="UniqueIdentifier NOT NULL")]
+		public System.Guid ResourceId
+		{
+			get
+			{
+				return this._ResourceId;
+			}
+			set
+			{
+				if ((this._ResourceId != value))
+				{
+					this.OnResourceIdChanging(value);
+					this.SendPropertyChanging();
+					this._ResourceId = value;
+					this.SendPropertyChanged("ResourceId");
+					this.OnResourceIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExpirationTime", DbType="DateTime")]
+		public System.Nullable<System.DateTime> ExpirationTime
+		{
+			get
+			{
+				return this._ExpirationTime;
+			}
+			set
+			{
+				if ((this._ExpirationTime != value))
+				{
+					this.OnExpirationTimeChanging(value);
+					this.SendPropertyChanging();
+					this._ExpirationTime = value;
+					this.SendPropertyChanged("ExpirationTime");
+					this.OnExpirationTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ResourceGroupId", DbType="UniqueIdentifier")]
+		public System.Nullable<System.Guid> ResourceGroupId
+		{
+			get
+			{
+				return this._ResourceGroupId;
+			}
+			set
+			{
+				if ((this._ResourceGroupId != value))
+				{
+					this.OnResourceGroupIdChanging(value);
+					this.SendPropertyChanging();
+					this._ResourceGroupId = value;
+					this.SendPropertyChanged("ResourceGroupId");
+					this.OnResourceGroupIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ResourceGroup2Id", DbType="UniqueIdentifier")]
+		public System.Nullable<System.Guid> ResourceGroup2Id
+		{
+			get
+			{
+				return this._ResourceGroup2Id;
+			}
+			set
+			{
+				if ((this._ResourceGroup2Id != value))
+				{
+					this.OnResourceGroup2IdChanging(value);
+					this.SendPropertyChanging();
+					this._ResourceGroup2Id = value;
+					this.SendPropertyChanged("ResourceGroup2Id");
+					this.OnResourceGroup2IdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_GroupName")]
+		public string GroupName
+		{
+			get
+			{
+				return this._GroupName;
+			}
+			set
+			{
+				if ((this._GroupName != value))
+				{
+					this.OnGroupNameChanging(value);
+					this.SendPropertyChanging();
+					this._GroupName = value;
+					this.SendPropertyChanged("GroupName");
+					this.OnGroupNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_GroupName2")]
+		public string GroupName2
+		{
+			get
+			{
+				return this._GroupName2;
+			}
+			set
+			{
+				if ((this._GroupName2 != value))
+				{
+					this.OnGroupName2Changing(value);
+					this.SendPropertyChanging();
+					this._GroupName2 = value;
+					this.SendPropertyChanged("GroupName2");
+					this.OnGroupName2Changed();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimClient_FactTask", Storage="_FactTasks", ThisKey="Id", OtherKey="LastClientId")]
+		public EntitySet<FactTask> FactTasks
+		{
+			get
+			{
+				return this._FactTasks;
+			}
+			set
+			{
+				this._FactTasks.Assign(value);
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimClient_FactClientInfo", Storage="_FactClientInfos", ThisKey="Id", OtherKey="ClientId")]
+		public EntitySet<FactClientInfo> FactClientInfos
+		{
+			get
+			{
+				return this._FactClientInfos;
+			}
+			set
+			{
+				this._FactClientInfos.Assign(value);
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+		
+		private void attach_FactTasks(FactTask entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimClient = this;
+		}
+		
+		private void detach_FactTasks(FactTask entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimClient = null;
+		}
+		
+		private void attach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimClient = this;
+		}
+		
+		private void detach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimClient = null;
+		}
+	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].FactTask")]
+	public partial class FactTask : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.Guid _TaskId;
+		
+		private long _TotalRuntime;
+		
+		private long _TotalWaitingTime;
+		
+		private long _TotalTransferTime;
+		
+		private int _NumCalculationRuns;
+		
+		private int _NumFails;
+		
+		private int _CoresRequired;
+		
+		private int _MemoryRequired;
+		
+		private int _Priority;
+		
+		private System.Nullable<System.Guid> _LastClientId;
+		
+		private System.Guid _JobId;
+		
+		private System.Nullable<System.DateTime> _EndTime;
+		
+		private global::HeuristicLab.Services.Hive.DataAccess.TaskState _TaskState;
+		
+		private string _Exception;
+		
+		private long _InitialWaitingTimeMs;
+		
+		private System.Nullable<System.DateTime> _StartTime;
+		
+		private EntityRef<DimClient> _DimClient;
+		
+		private EntityRef<DimJob> _DimJob;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnTaskIdChanging(System.Guid value);
+    partial void OnTaskIdChanged();
+    partial void OnCalculatingTimeChanging(long value);
+    partial void OnCalculatingTimeChanged();
+    partial void OnWaitingTimeChanging(long value);
+    partial void OnWaitingTimeChanged();
+    partial void OnTransferTimeChanging(long value);
+    partial void OnTransferTimeChanged();
+    partial void OnNumCalculationRunsChanging(int value);
+    partial void OnNumCalculationRunsChanged();
+    partial void OnNumRetriesChanging(int value);
+    partial void OnNumRetriesChanged();
+    partial void OnCoresRequiredChanging(int value);
+    partial void OnCoresRequiredChanged();
+    partial void OnMemoryRequiredChanging(int value);
+    partial void OnMemoryRequiredChanged();
+    partial void OnPriorityChanging(int value);
+    partial void OnPriorityChanged();
+    partial void OnLastClientIdChanging(System.Nullable<System.Guid> value);
+    partial void OnLastClientIdChanged();
+    partial void OnJobIdChanging(System.Guid value);
+    partial void OnJobIdChanged();
+    partial void OnEndTimeChanging(System.Nullable<System.DateTime> value);
+    partial void OnEndTimeChanged();
+    partial void OnTaskStateChanging(global::HeuristicLab.Services.Hive.DataAccess.TaskState value);
+    partial void OnTaskStateChanged();
+    partial void OnExceptionChanging(string value);
+    partial void OnExceptionChanged();
+    partial void OnInitialWaitingTimeChanging(long value);
+    partial void OnInitialWaitingTimeChanged();
+    partial void OnStartTimeChanging(System.Nullable<System.DateTime> value);
+    partial void OnStartTimeChanged();
+    #endregion
+		
+		public FactTask()
+		{
+			this._DimClient = default(EntityRef<DimClient>);
+			this._DimJob = default(EntityRef<DimJob>);
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TaskId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
+		public System.Guid TaskId
+		{
+			get
+			{
+				return this._TaskId;
+			}
+			set
+			{
+				if ((this._TaskId != value))
+				{
+					this.OnTaskIdChanging(value);
+					this.SendPropertyChanging();
+					this._TaskId = value;
+					this.SendPropertyChanged("TaskId");
+					this.OnTaskIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalRuntime", DbType="INT NOT NULL")]
+		public long CalculatingTime
+		{
+			get
+			{
+				return this._TotalRuntime;
+			}
+			set
+			{
+				if ((this._TotalRuntime != value))
+				{
+					this.OnCalculatingTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalRuntime = value;
+					this.SendPropertyChanged("CalculatingTime");
+					this.OnCalculatingTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalWaitingTime", DbType="INT NOT NULL")]
+		public long WaitingTime
+		{
+			get
+			{
+				return this._TotalWaitingTime;
+			}
+			set
+			{
+				if ((this._TotalWaitingTime != value))
+				{
+					this.OnWaitingTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalWaitingTime = value;
+					this.SendPropertyChanged("WaitingTime");
+					this.OnWaitingTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTransferTime", DbType="INT NOT NULL")]
+		public long TransferTime
+		{
+			get
+			{
+				return this._TotalTransferTime;
+			}
+			set
+			{
+				if ((this._TotalTransferTime != value))
+				{
+					this.OnTransferTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTransferTime = value;
+					this.SendPropertyChanged("TransferTime");
+					this.OnTransferTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_NumCalculationRuns", DbType="Int NOT NULL")]
+		public int NumCalculationRuns
+		{
+			get
+			{
+				return this._NumCalculationRuns;
+			}
+			set
+			{
+				if ((this._NumCalculationRuns != value))
+				{
+					this.OnNumCalculationRunsChanging(value);
+					this.SendPropertyChanging();
+					this._NumCalculationRuns = value;
+					this.SendPropertyChanged("NumCalculationRuns");
+					this.OnNumCalculationRunsChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_NumFails", DbType="Int NOT NULL")]
+		public int NumRetries
+		{
+			get
+			{
+				return this._NumFails;
+			}
+			set
+			{
+				if ((this._NumFails != value))
+				{
+					this.OnNumRetriesChanging(value);
+					this.SendPropertyChanging();
+					this._NumFails = value;
+					this.SendPropertyChanged("NumRetries");
+					this.OnNumRetriesChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CoresRequired", DbType="Int NOT NULL")]
+		public int CoresRequired
+		{
+			get
+			{
+				return this._CoresRequired;
+			}
+			set
+			{
+				if ((this._CoresRequired != value))
+				{
+					this.OnCoresRequiredChanging(value);
+					this.SendPropertyChanging();
+					this._CoresRequired = value;
+					this.SendPropertyChanged("CoresRequired");
+					this.OnCoresRequiredChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_MemoryRequired", DbType="Int NOT NULL")]
+		public int MemoryRequired
+		{
+			get
+			{
+				return this._MemoryRequired;
+			}
+			set
+			{
+				if ((this._MemoryRequired != value))
+				{
+					this.OnMemoryRequiredChanging(value);
+					this.SendPropertyChanging();
+					this._MemoryRequired = value;
+					this.SendPropertyChanged("MemoryRequired");
+					this.OnMemoryRequiredChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Priority", DbType="Int NOT NULL")]
+		public int Priority
+		{
+			get
+			{
+				return this._Priority;
+			}
+			set
+			{
+				if ((this._Priority != value))
+				{
+					this.OnPriorityChanging(value);
+					this.SendPropertyChanging();
+					this._Priority = value;
+					this.SendPropertyChanged("Priority");
+					this.OnPriorityChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_LastClientId", DbType="UniqueIdentifier NOT NULL")]
+		public System.Nullable<System.Guid> LastClientId
+		{
+			get
+			{
+				return this._LastClientId;
+			}
+			set
+			{
+				if ((this._LastClientId != value))
+				{
+					if (this._DimClient.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnLastClientIdChanging(value);
+					this.SendPropertyChanging();
+					this._LastClientId = value;
+					this.SendPropertyChanged("LastClientId");
+					this.OnLastClientIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_JobId", DbType="UniqueIdentifier NOT NULL")]
+		public System.Guid JobId
+		{
+			get
+			{
+				return this._JobId;
+			}
+			set
+			{
+				if ((this._JobId != value))
+				{
+					if (this._DimJob.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnJobIdChanging(value);
+					this.SendPropertyChanging();
+					this._JobId = value;
+					this.SendPropertyChanged("JobId");
+					this.OnJobIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_EndTime", DbType="DateTime NOT NULL")]
+		public System.Nullable<System.DateTime> EndTime
+		{
+			get
+			{
+				return this._EndTime;
+			}
+			set
+			{
+				if ((this._EndTime != value))
+				{
+					this.OnEndTimeChanging(value);
+					this.SendPropertyChanging();
+					this._EndTime = value;
+					this.SendPropertyChanged("EndTime");
+					this.OnEndTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TaskState", DbType="varchar(30)", CanBeNull=false)]
+		public global::HeuristicLab.Services.Hive.DataAccess.TaskState TaskState
+		{
+			get
+			{
+				return this._TaskState;
+			}
+			set
+			{
+				if ((this._TaskState != value))
+				{
+					this.OnTaskStateChanging(value);
+					this.SendPropertyChanging();
+					this._TaskState = value;
+					this.SendPropertyChanged("TaskState");
+					this.OnTaskStateChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Exception", DbType="varchar(MAX)")]
+		public string Exception
+		{
+			get
+			{
+				return this._Exception;
+			}
+			set
+			{
+				if ((this._Exception != value))
+				{
+					this.OnExceptionChanging(value);
+					this.SendPropertyChanging();
+					this._Exception = value;
+					this.SendPropertyChanged("Exception");
+					this.OnExceptionChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_InitialWaitingTimeMs", DbType="INT")]
+		public long InitialWaitingTime
+		{
+			get
+			{
+				return this._InitialWaitingTimeMs;
+			}
+			set
+			{
+				if ((this._InitialWaitingTimeMs != value))
+				{
+					this.OnInitialWaitingTimeChanging(value);
+					this.SendPropertyChanging();
+					this._InitialWaitingTimeMs = value;
+					this.SendPropertyChanged("InitialWaitingTime");
+					this.OnInitialWaitingTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_StartTime")]
+		public System.Nullable<System.DateTime> StartTime
+		{
+			get
+			{
+				return this._StartTime;
+			}
+			set
+			{
+				if ((this._StartTime != value))
+				{
+					this.OnStartTimeChanging(value);
+					this.SendPropertyChanging();
+					this._StartTime = value;
+					this.SendPropertyChanged("StartTime");
+					this.OnStartTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimClient_FactTask", Storage="_DimClient", ThisKey="LastClientId", OtherKey="Id", IsForeignKey=true)]
+		public DimClient DimClient
+		{
+			get
+			{
+				return this._DimClient.Entity;
+			}
+			set
+			{
+				DimClient previousValue = this._DimClient.Entity;
+				if (((previousValue != value) 
+							|| (this._DimClient.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._DimClient.Entity = null;
+						previousValue.FactTasks.Remove(this);
+					}
+					this._DimClient.Entity = value;
+					if ((value != null))
+					{
+						value.FactTasks.Add(this);
+						this._LastClientId = value.Id;
+					}
+					else
+					{
+						this._LastClientId = default(Nullable<System.Guid>);
+					}
+					this.SendPropertyChanged("DimClient");
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimJob_FactTask", Storage="_DimJob", ThisKey="JobId", OtherKey="JobId", IsForeignKey=true)]
+		public DimJob DimJob
+		{
+			get
+			{
+				return this._DimJob.Entity;
+			}
+			set
+			{
+				DimJob previousValue = this._DimJob.Entity;
+				if (((previousValue != value) 
+							|| (this._DimJob.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._DimJob.Entity = null;
+						previousValue.FactTasks.Remove(this);
+					}
+					this._DimJob.Entity = value;
+					if ((value != null))
+					{
+						value.FactTasks.Add(this);
+						this._JobId = value.JobId;
+					}
+					else
+					{
+						this._JobId = default(System.Guid);
+					}
+					this.SendPropertyChanged("DimJob");
+				}
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].DimJob")]
+	public partial class DimJob : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.Guid _JobId;
+		
+		private System.Guid _UserId;
+		
+		private string _JobName;
+		
+		private string _UserName;
+		
+		private System.DateTime _DateCreated;
+		
+		private int _TotalTasks;
+		
+		private int _CompletedTasks;
+		
+		private System.Nullable<System.DateTime> _DateCompleted;
+		
+		private EntitySet<FactTask> _FactTasks;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnJobIdChanging(System.Guid value);
+    partial void OnJobIdChanged();
+    partial void OnUserIdChanging(System.Guid value);
+    partial void OnUserIdChanged();
+    partial void OnJobNameChanging(string value);
+    partial void OnJobNameChanged();
+    partial void OnUserNameChanging(string value);
+    partial void OnUserNameChanged();
+    partial void OnDateCreatedChanging(System.DateTime value);
+    partial void OnDateCreatedChanged();
+    partial void OnTotalTasksChanging(int value);
+    partial void OnTotalTasksChanged();
+    partial void OnCompletedTasksChanging(int value);
+    partial void OnCompletedTasksChanged();
+    partial void OnDateCompletedChanging(System.Nullable<System.DateTime> value);
+    partial void OnDateCompletedChanged();
+    #endregion
+		
+		public DimJob()
+		{
+			this._FactTasks = new EntitySet<FactTask>(new Action<FactTask>(this.attach_FactTasks), new Action<FactTask>(this.detach_FactTasks));
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_JobId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
+		public System.Guid JobId
+		{
+			get
+			{
+				return this._JobId;
+			}
+			set
+			{
+				if ((this._JobId != value))
+				{
+					this.OnJobIdChanging(value);
+					this.SendPropertyChanging();
+					this._JobId = value;
+					this.SendPropertyChanged("JobId");
+					this.OnJobIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserId", DbType="UniqueIdentifier NOT NULL")]
+		public System.Guid UserId
+		{
+			get
+			{
+				return this._UserId;
+			}
+			set
+			{
+				if ((this._UserId != value))
+				{
+					this.OnUserIdChanging(value);
+					this.SendPropertyChanging();
+					this._UserId = value;
+					this.SendPropertyChanged("UserId");
+					this.OnUserIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_JobName", DbType="VarChar(MAX) NOT NULL", CanBeNull=false)]
+		public string JobName
+		{
+			get
+			{
+				return this._JobName;
+			}
+			set
+			{
+				if ((this._JobName != value))
+				{
+					this.OnJobNameChanging(value);
+					this.SendPropertyChanging();
+					this._JobName = value;
+					this.SendPropertyChanged("JobName");
+					this.OnJobNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserName", DbType="VarChar(MAX) NOT NULL", CanBeNull=false)]
+		public string UserName
+		{
+			get
+			{
+				return this._UserName;
+			}
+			set
+			{
+				if ((this._UserName != value))
+				{
+					this.OnUserNameChanging(value);
+					this.SendPropertyChanging();
+					this._UserName = value;
+					this.SendPropertyChanged("UserName");
+					this.OnUserNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DateCreated", DbType="DateTime NOT NULL")]
+		public System.DateTime DateCreated
+		{
+			get
+			{
+				return this._DateCreated;
+			}
+			set
+			{
+				if ((this._DateCreated != value))
+				{
+					this.OnDateCreatedChanging(value);
+					this.SendPropertyChanging();
+					this._DateCreated = value;
+					this.SendPropertyChanged("DateCreated");
+					this.OnDateCreatedChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTasks", DbType="INT NOT NULL")]
+		public int TotalTasks
+		{
+			get
+			{
+				return this._TotalTasks;
+			}
+			set
+			{
+				if ((this._TotalTasks != value))
+				{
+					this.OnTotalTasksChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTasks = value;
+					this.SendPropertyChanged("TotalTasks");
+					this.OnTotalTasksChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CompletedTasks", DbType="INT NOT NULL")]
+		public int CompletedTasks
+		{
+			get
+			{
+				return this._CompletedTasks;
+			}
+			set
+			{
+				if ((this._CompletedTasks != value))
+				{
+					this.OnCompletedTasksChanging(value);
+					this.SendPropertyChanging();
+					this._CompletedTasks = value;
+					this.SendPropertyChanged("CompletedTasks");
+					this.OnCompletedTasksChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DateCompleted", DbType="DateTime NULL")]
+		public System.Nullable<System.DateTime> DateCompleted
+		{
+			get
+			{
+				return this._DateCompleted;
+			}
+			set
+			{
+				if ((this._DateCompleted != value))
+				{
+					this.OnDateCompletedChanging(value);
+					this.SendPropertyChanging();
+					this._DateCompleted = value;
+					this.SendPropertyChanged("DateCompleted");
+					this.OnDateCompletedChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimJob_FactTask", Storage="_FactTasks", ThisKey="JobId", OtherKey="JobId")]
+		public EntitySet<FactTask> FactTasks
+		{
+			get
+			{
+				return this._FactTasks;
+			}
+			set
+			{
+				this._FactTasks.Assign(value);
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+		
+		private void attach_FactTasks(FactTask entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimJob = this;
+		}
+		
+		private void detach_FactTasks(FactTask entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimJob = null;
+		}
+	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].DimTime")]
+	public partial class DimTime : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.DateTime _Time;
+		
+		private System.DateTime _Hour;
+		
+		private System.DateTime _Day;
+		
+		private System.DateTime _Month;
+		
+		private System.DateTime _Year;
+		
+		private System.DateTime _Minute;
+		
+		private EntitySet<FactClientInfo> _FactClientInfos;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnTimeChanging(System.DateTime value);
+    partial void OnTimeChanged();
+    partial void OnHourChanging(System.DateTime value);
+    partial void OnHourChanged();
+    partial void OnDayChanging(System.DateTime value);
+    partial void OnDayChanged();
+    partial void OnMonthChanging(System.DateTime value);
+    partial void OnMonthChanged();
+    partial void OnYearChanging(System.DateTime value);
+    partial void OnYearChanged();
+    partial void OnMinuteChanging(System.DateTime value);
+    partial void OnMinuteChanged();
+    #endregion
+		
+		public DimTime()
+		{
+			this._FactClientInfos = new EntitySet<FactClientInfo>(new Action<FactClientInfo>(this.attach_FactClientInfos), new Action<FactClientInfo>(this.detach_FactClientInfos));
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Time", DbType="DateTime NOT NULL", IsPrimaryKey=true)]
+		public System.DateTime Time
+		{
+			get
+			{
+				return this._Time;
+			}
+			set
+			{
+				if ((this._Time != value))
+				{
+					this.OnTimeChanging(value);
+					this.SendPropertyChanging();
+					this._Time = value;
+					this.SendPropertyChanged("Time");
+					this.OnTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Hour", DbType="DateTime NOT NULL")]
+		public System.DateTime Hour
+		{
+			get
+			{
+				return this._Hour;
+			}
+			set
+			{
+				if ((this._Hour != value))
+				{
+					this.OnHourChanging(value);
+					this.SendPropertyChanging();
+					this._Hour = value;
+					this.SendPropertyChanged("Hour");
+					this.OnHourChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Day", DbType="Date NOT NULL")]
+		public System.DateTime Day
+		{
+			get
+			{
+				return this._Day;
+			}
+			set
+			{
+				if ((this._Day != value))
+				{
+					this.OnDayChanging(value);
+					this.SendPropertyChanging();
+					this._Day = value;
+					this.SendPropertyChanged("Day");
+					this.OnDayChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Month", DbType="Date NOT NULL")]
+		public System.DateTime Month
+		{
+			get
+			{
+				return this._Month;
+			}
+			set
+			{
+				if ((this._Month != value))
+				{
+					this.OnMonthChanging(value);
+					this.SendPropertyChanging();
+					this._Month = value;
+					this.SendPropertyChanged("Month");
+					this.OnMonthChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Year", DbType="Date NOT NULL")]
+		public System.DateTime Year
+		{
+			get
+			{
+				return this._Year;
+			}
+			set
+			{
+				if ((this._Year != value))
+				{
+					this.OnYearChanging(value);
+					this.SendPropertyChanging();
+					this._Year = value;
+					this.SendPropertyChanged("Year");
+					this.OnYearChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Minute")]
+		public System.DateTime Minute
+		{
+			get
+			{
+				return this._Minute;
+			}
+			set
+			{
+				if ((this._Minute != value))
+				{
+					this.OnMinuteChanging(value);
+					this.SendPropertyChanging();
+					this._Minute = value;
+					this.SendPropertyChanged("Minute");
+					this.OnMinuteChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimTime_FactClientInfo", Storage="_FactClientInfos", ThisKey="Time", OtherKey="Time")]
+		public EntitySet<FactClientInfo> FactClientInfos
+		{
+			get
+			{
+				return this._FactClientInfos;
+			}
+			set
+			{
+				this._FactClientInfos.Assign(value);
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+		
+		private void attach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimTime = this;
+		}
+		
+		private void detach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimTime = null;
+		}
+	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].DimUser")]
+	public partial class DimUser : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.Guid _UserId;
+		
+		private string _Name;
+		
+		private EntitySet<FactClientInfo> _FactClientInfos;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnUserIdChanging(System.Guid value);
+    partial void OnUserIdChanged();
+    partial void OnNameChanging(string value);
+    partial void OnNameChanged();
+    #endregion
+		
+		public DimUser()
+		{
+			this._FactClientInfos = new EntitySet<FactClientInfo>(new Action<FactClientInfo>(this.attach_FactClientInfos), new Action<FactClientInfo>(this.detach_FactClientInfos));
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
+		public System.Guid UserId
+		{
+			get
+			{
+				return this._UserId;
+			}
+			set
+			{
+				if ((this._UserId != value))
+				{
+					this.OnUserIdChanging(value);
+					this.SendPropertyChanging();
+					this._UserId = value;
+					this.SendPropertyChanged("UserId");
+					this.OnUserIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Name", DbType="VarChar(MAX) NOT NULL", CanBeNull=false)]
+		public string Name
+		{
+			get
+			{
+				return this._Name;
+			}
+			set
+			{
+				if ((this._Name != value))
+				{
+					this.OnNameChanging(value);
+					this.SendPropertyChanging();
+					this._Name = value;
+					this.SendPropertyChanged("Name");
+					this.OnNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimUser_FactClientInfo", Storage="_FactClientInfos", ThisKey="UserId", OtherKey="UserId")]
+		public EntitySet<FactClientInfo> FactClientInfos
+		{
+			get
+			{
+				return this._FactClientInfos;
+			}
+			set
+			{
+				this._FactClientInfos.Assign(value);
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+		
+		private void attach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimUser = this;
+		}
+		
+		private void detach_FactClientInfos(FactClientInfo entity)
+		{
+			this.SendPropertyChanging();
+			entity.DimUser = null;
+		}
+	}
+	
+	[global::System.Data.Linq.Mapping.TableAttribute(Name="[statistics].FactClientInfo")]
+	public partial class FactClientInfo : INotifyPropertyChanging, INotifyPropertyChanged
+	{
+		
+		private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
+		
+		private System.Guid _ClientId;
+		
+		private System.DateTime _Time;
+		
+		private System.Guid _UserId;
+		
+		private int _NumUsedCores;
+		
+		private int _NumTotalCores;
+		
+		private int _UsedMemory;
+		
+		private int _TotalMemory;
+		
+		private double _CpuUtilization;
+		
+		private long _TotalTimeIdle;
+		
+		private long _TotalTimeOffline;
+		
+		private long _TotalTimeUnavailable;
+		
+		private global::HeuristicLab.Services.Hive.DataAccess.SlaveState _SlaveState;
+		
+		private bool _IsAllowedToCalculate;
+		
+		private EntityRef<DimClient> _DimClient;
+		
+		private EntityRef<DimTime> _DimTime;
+		
+		private EntityRef<DimUser> _DimUser;
+		
+    #region Extensibility Method Definitions
+    partial void OnLoaded();
+    partial void OnValidate(System.Data.Linq.ChangeAction action);
+    partial void OnCreated();
+    partial void OnClientIdChanging(System.Guid value);
+    partial void OnClientIdChanged();
+    partial void OnTimeChanging(System.DateTime value);
+    partial void OnTimeChanged();
+    partial void OnUserIdChanging(System.Guid value);
+    partial void OnUserIdChanged();
+    partial void OnNumUsedCoresChanging(int value);
+    partial void OnNumUsedCoresChanged();
+    partial void OnNumTotalCoresChanging(int value);
+    partial void OnNumTotalCoresChanged();
+    partial void OnUsedMemoryChanging(int value);
+    partial void OnUsedMemoryChanged();
+    partial void OnTotalMemoryChanging(int value);
+    partial void OnTotalMemoryChanged();
+    partial void OnCpuUtilizationChanging(double value);
+    partial void OnCpuUtilizationChanged();
+    partial void OnIdleTimeChanging(long value);
+    partial void OnIdleTimeChanged();
+    partial void OnOfflineTimeChanging(long value);
+    partial void OnOfflineTimeChanged();
+    partial void OnUnavailableTimeChanging(long value);
+    partial void OnUnavailableTimeChanged();
+    partial void OnSlaveStateChanging(global::HeuristicLab.Services.Hive.DataAccess.SlaveState value);
+    partial void OnSlaveStateChanged();
+    partial void OnIsAllowedToCalculateChanging(bool value);
+    partial void OnIsAllowedToCalculateChanged();
+    #endregion
+		
+		public FactClientInfo()
+		{
+			this._DimClient = default(EntityRef<DimClient>);
+			this._DimTime = default(EntityRef<DimTime>);
+			this._DimUser = default(EntityRef<DimUser>);
+			OnCreated();
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ClientId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
+		public System.Guid ClientId
+		{
+			get
+			{
+				return this._ClientId;
+			}
+			set
+			{
+				if ((this._ClientId != value))
+				{
+					if (this._DimClient.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnClientIdChanging(value);
+					this.SendPropertyChanging();
+					this._ClientId = value;
+					this.SendPropertyChanged("ClientId");
+					this.OnClientIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Time", DbType="DateTime NOT NULL", IsPrimaryKey=true)]
+		public System.DateTime Time
+		{
+			get
+			{
+				return this._Time;
+			}
+			set
+			{
+				if ((this._Time != value))
+				{
+					if (this._DimTime.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnTimeChanging(value);
+					this.SendPropertyChanging();
+					this._Time = value;
+					this.SendPropertyChanged("Time");
+					this.OnTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UserId", DbType="UniqueIdentifier NULL", IsPrimaryKey=true)]
+		public System.Guid UserId
+		{
+			get
+			{
+				return this._UserId;
+			}
+			set
+			{
+				if ((this._UserId != value))
+				{
+					if (this._DimUser.HasLoadedOrAssignedValue)
+					{
+						throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
+					}
+					this.OnUserIdChanging(value);
+					this.SendPropertyChanging();
+					this._UserId = value;
+					this.SendPropertyChanged("UserId");
+					this.OnUserIdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_NumUsedCores", DbType="Int NOT NULL")]
+		public int NumUsedCores
+		{
+			get
+			{
+				return this._NumUsedCores;
+			}
+			set
+			{
+				if ((this._NumUsedCores != value))
+				{
+					this.OnNumUsedCoresChanging(value);
+					this.SendPropertyChanging();
+					this._NumUsedCores = value;
+					this.SendPropertyChanged("NumUsedCores");
+					this.OnNumUsedCoresChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_NumTotalCores", DbType="Int NOT NULL")]
+		public int NumTotalCores
+		{
+			get
+			{
+				return this._NumTotalCores;
+			}
+			set
+			{
+				if ((this._NumTotalCores != value))
+				{
+					this.OnNumTotalCoresChanging(value);
+					this.SendPropertyChanging();
+					this._NumTotalCores = value;
+					this.SendPropertyChanged("NumTotalCores");
+					this.OnNumTotalCoresChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UsedMemory", DbType="Int NOT NULL")]
+		public int UsedMemory
+		{
+			get
+			{
+				return this._UsedMemory;
+			}
+			set
+			{
+				if ((this._UsedMemory != value))
+				{
+					this.OnUsedMemoryChanging(value);
+					this.SendPropertyChanging();
+					this._UsedMemory = value;
+					this.SendPropertyChanged("UsedMemory");
+					this.OnUsedMemoryChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalMemory", DbType="Int NOT NULL")]
+		public int TotalMemory
+		{
+			get
+			{
+				return this._TotalMemory;
+			}
+			set
+			{
+				if ((this._TotalMemory != value))
+				{
+					this.OnTotalMemoryChanging(value);
+					this.SendPropertyChanging();
+					this._TotalMemory = value;
+					this.SendPropertyChanged("TotalMemory");
+					this.OnTotalMemoryChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CpuUtilization", DbType="Float NOT NULL")]
+		public double CpuUtilization
+		{
+			get
+			{
+				return this._CpuUtilization;
+			}
+			set
+			{
+				if ((this._CpuUtilization != value))
+				{
+					this.OnCpuUtilizationChanging(value);
+					this.SendPropertyChanging();
+					this._CpuUtilization = value;
+					this.SendPropertyChanged("CpuUtilization");
+					this.OnCpuUtilizationChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeIdle", DbType="INT NOT NULL")]
+		public long IdleTime
+		{
+			get
+			{
+				return this._TotalTimeIdle;
+			}
+			set
+			{
+				if ((this._TotalTimeIdle != value))
+				{
+					this.OnIdleTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTimeIdle = value;
+					this.SendPropertyChanged("IdleTime");
+					this.OnIdleTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeOffline", DbType="INT NOT NULL")]
+		public long OfflineTime
+		{
+			get
+			{
+				return this._TotalTimeOffline;
+			}
+			set
+			{
+				if ((this._TotalTimeOffline != value))
+				{
+					this.OnOfflineTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTimeOffline = value;
+					this.SendPropertyChanged("OfflineTime");
+					this.OnOfflineTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeUnavailable", DbType="INT NOT NULL")]
+		public long UnavailableTime
+		{
+			get
+			{
+				return this._TotalTimeUnavailable;
+			}
+			set
+			{
+				if ((this._TotalTimeUnavailable != value))
+				{
+					this.OnUnavailableTimeChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTimeUnavailable = value;
+					this.SendPropertyChanged("UnavailableTime");
+					this.OnUnavailableTimeChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_SlaveState", DbType="VarChar(15)", CanBeNull=false)]
+		public global::HeuristicLab.Services.Hive.DataAccess.SlaveState SlaveState
+		{
+			get
+			{
+				return this._SlaveState;
+			}
+			set
+			{
+				if ((this._SlaveState != value))
+				{
+					this.OnSlaveStateChanging(value);
+					this.SendPropertyChanging();
+					this._SlaveState = value;
+					this.SendPropertyChanged("SlaveState");
+					this.OnSlaveStateChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_IsAllowedToCalculate", DbType="BIT")]
+		public bool IsAllowedToCalculate
+		{
+			get
+			{
+				return this._IsAllowedToCalculate;
+			}
+			set
+			{
+				if ((this._IsAllowedToCalculate != value))
+				{
+					this.OnIsAllowedToCalculateChanging(value);
+					this.SendPropertyChanging();
+					this._IsAllowedToCalculate = value;
+					this.SendPropertyChanged("IsAllowedToCalculate");
+					this.OnIsAllowedToCalculateChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimClient_FactClientInfo", Storage="_DimClient", ThisKey="ClientId", OtherKey="Id", IsForeignKey=true)]
+		public DimClient DimClient
+		{
+			get
+			{
+				return this._DimClient.Entity;
+			}
+			set
+			{
+				DimClient previousValue = this._DimClient.Entity;
+				if (((previousValue != value) 
+							|| (this._DimClient.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._DimClient.Entity = null;
+						previousValue.FactClientInfos.Remove(this);
+					}
+					this._DimClient.Entity = value;
+					if ((value != null))
+					{
+						value.FactClientInfos.Add(this);
+						this._ClientId = value.Id;
+					}
+					else
+					{
+						this._ClientId = default(System.Guid);
+					}
+					this.SendPropertyChanged("DimClient");
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimTime_FactClientInfo", Storage="_DimTime", ThisKey="Time", OtherKey="Time", IsForeignKey=true)]
+		public DimTime DimTime
+		{
+			get
+			{
+				return this._DimTime.Entity;
+			}
+			set
+			{
+				DimTime previousValue = this._DimTime.Entity;
+				if (((previousValue != value) 
+							|| (this._DimTime.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._DimTime.Entity = null;
+						previousValue.FactClientInfos.Remove(this);
+					}
+					this._DimTime.Entity = value;
+					if ((value != null))
+					{
+						value.FactClientInfos.Add(this);
+						this._Time = value.Time;
+					}
+					else
+					{
+						this._Time = default(System.DateTime);
+					}
+					this.SendPropertyChanged("DimTime");
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.AssociationAttribute(Name="DimUser_FactClientInfo", Storage="_DimUser", ThisKey="UserId", OtherKey="UserId", IsForeignKey=true)]
+		public DimUser DimUser
+		{
+			get
+			{
+				return this._DimUser.Entity;
+			}
+			set
+			{
+				DimUser previousValue = this._DimUser.Entity;
+				if (((previousValue != value) 
+							|| (this._DimUser.HasLoadedOrAssignedValue == false)))
+				{
+					this.SendPropertyChanging();
+					if ((previousValue != null))
+					{
+						this._DimUser.Entity = null;
+						previousValue.FactClientInfos.Remove(this);
+					}
+					this._DimUser.Entity = value;
+					if ((value != null))
+					{
+						value.FactClientInfos.Add(this);
+						this._UserId = value.UserId;
+					}
+					else
+					{
+						this._UserId = default(System.Guid);
+					}
+					this.SendPropertyChanged("DimUser");
+				}
+			}
+		}
+		
+		public event PropertyChangingEventHandler PropertyChanging;
+		
+		public event PropertyChangedEventHandler PropertyChanged;
+		
+		protected virtual void SendPropertyChanging()
+		{
+			if ((this.PropertyChanging != null))
+			{
+				this.PropertyChanging(this, emptyChangingEventArgs);
+			}
+		}
+		
+		protected virtual void SendPropertyChanged(String propertyName)
+		{
+			if ((this.PropertyChanged != null))
+			{
+				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+			}
+		}
+	}
 }
 #pragma warning restore 1591
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/ITransactionManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/ITransactionManager.cs	(revision 12877)
+++ 	(revision )
@@ -1,29 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public interface ITransactionManager {
-    void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false);
-    T UseTransaction<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false);
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IGenericDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IGenericDao.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IGenericDao.cs	(revision 12878)
@@ -0,0 +1,45 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Interfaces {
+  public interface IGenericDao<in PK, T> where T : class {
+    T GetById(PK id);
+    IQueryable<T> Get(Expression<Func<T, bool>> predicate);
+    IQueryable<T> GetAll();
+    T Save(T entity);
+    IEnumerable<T> Save(params T[] entities);
+    IEnumerable<T> Save(IEnumerable<T> entities);
+    T SaveOrAttach(T entity);
+    IEnumerable<T> SaveOrAttach(params T[] entities);
+    IEnumerable<T> SaveOrAttach(IEnumerable<T> entities);
+    void Delete(PK id);
+    void Delete(IEnumerable<PK> ids);
+    void Delete(T entity);
+    void Delete(IEnumerable<T> entities);
+    bool Exists(T entity);
+    bool Exists(PK id);
+  }
+}
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IHiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IHiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,149 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq.Expressions;
-using HeuristicLab.Services.Hive.Common.DataTransfer;
-using DT = HeuristicLab.Services.Hive.Common.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public interface IHiveDao {
-    #region Job Methods
-    DT.Job GetJob(Guid id);
-    IEnumerable<DT.Job> GetJobs(Expression<Func<Job, bool>> predicate);
-    Guid AddJob(DT.Job dto);
-    void UpdateJob(DT.Job dto);
-    void DeleteJob(Guid id);
-    IEnumerable<DT.Job> GetWaitingJobs(DT.Slave slave, int count);
-    IEnumerable<DT.Job> GetParentJobs(IEnumerable<Guid> resourceIds, int count, bool finished);
-    DT.Job UpdateJobState(Guid jobId, JobState jobState, Guid? slaveId, Guid? userId, string exception);
-    #endregion
-
-    #region JobData Methods
-    DT.JobData GetJobData(Guid id);
-    IEnumerable<DT.JobData> GetJobDatas(Expression<Func<JobData, bool>> predicate);
-    Guid AddJobData(DT.JobData dto);
-    void UpdateJobData(DT.JobData dto);
-    void DeleteJobData(Guid id);
-    #endregion
-
-    #region StateLog Methods
-    DT.StateLog GetStateLog(Guid id);
-    IEnumerable<DT.StateLog> GetStateLogs(Expression<Func<StateLog, bool>> predicate);
-    Guid AddStateLog(DT.StateLog dto);
-    void UpdateStateLog(DT.StateLog dto);
-    void DeleteStateLog(Guid id);
-    #endregion
-
-    #region HiveExperiment Methods
-    DT.HiveExperiment GetHiveExperiment(Guid id);
-    IEnumerable<DT.HiveExperiment> GetHiveExperiments(Expression<Func<HiveExperiment, bool>> predicate);
-    Guid AddHiveExperiment(DT.HiveExperiment dto);
-    void UpdateHiveExperiment(DT.HiveExperiment dto);
-    void DeleteHiveExperiment(Guid id);
-    #endregion
-
-    #region HiveExperimentPermission Methods
-    DT.HiveExperimentPermission GetHiveExperimentPermission(Guid hiveExperimentId, Guid grantedUserId);
-    IEnumerable<DT.HiveExperimentPermission> GetHiveExperimentPermissions(Expression<Func<HiveExperimentPermission, bool>> predicate);
-    void AddHiveExperimentPermission(DT.HiveExperimentPermission dto);
-    void UpdateHiveExperimentPermission(DT.HiveExperimentPermission dto);
-    void DeleteHiveExperimentPermission(Guid hiveExperimentId, Guid grantedUserId);
-    void SetHiveExperimentPermission(Guid hiveExperimentId, Guid grantedByUserId, Guid grantedUserId, Permission permission);
-    #endregion
-
-    #region Plugin Methods
-    DT.Plugin GetPlugin(Guid id);
-    IEnumerable<DT.Plugin> GetPlugins(Expression<Func<Plugin, bool>> predicate);
-    Guid AddPlugin(DT.Plugin dto);
-    void UpdatePlugin(DT.Plugin dto);
-    void DeletePlugin(Guid id);
-    #endregion
-
-    #region PluginData Methods
-    DT.PluginData GetPluginData(Guid id);
-    IEnumerable<DT.PluginData> GetPluginDatas(Expression<Func<PluginData, bool>> predicate);
-    Guid AddPluginData(DT.PluginData dto);
-    void UpdatePluginData(DT.PluginData dto);
-    void DeletePluginData(Guid id);
-    #endregion
-
-    #region Calendar Methods
-    #endregion
-
-    #region Slave Methods
-    DT.Slave GetSlave(Guid id);
-    IEnumerable<DT.Slave> GetSlaves(Expression<Func<Slave, bool>> predicate);
-    Guid AddSlave(DT.Slave dto);
-    void UpdateSlave(DT.Slave dto);
-    void DeleteSlave(Guid id);
-    #endregion
-
-    #region SlaveGroup Methods
-    DT.SlaveGroup GetSlaveGroup(Guid id);
-    IEnumerable<DT.SlaveGroup> GetSlaveGroups(Expression<Func<SlaveGroup, bool>> predicate);
-    Guid AddSlaveGroup(DT.SlaveGroup dto);
-    void UpdateSlaveGroup(DT.SlaveGroup dto);
-    void DeleteSlaveGroup(Guid id);
-    #endregion
-
-    #region Resource Methods
-    DT.Resource GetResource(Guid id);
-    IEnumerable<DT.Resource> GetResources(Expression<Func<Resource, bool>> predicate);
-    Guid AddResource(DT.Resource dto);
-    void UpdateResource(DT.Resource dto);
-    void DeleteResource(Guid id);
-    void AssignJobToResource(Guid jobId, Guid resourceId);
-    IEnumerable<DT.Resource> GetAssignedResources(Guid jobId);
-    IEnumerable<DT.Resource> GetParentResources(Guid resourceId);
-    IEnumerable<DT.Resource> GetChildResources(Guid resourceId);
-    IEnumerable<DT.Job> GetJobsByResourceId(Guid resourceId);
-    #endregion
-
-    #region Authorization Methods
-    Permission GetPermissionForJob(Guid jobId, Guid userId);
-    Permission GetPermissionForExperiment(Guid experimentId, Guid userId);
-    Guid GetExperimentForJob(Guid jobId);
-    #endregion
-
-    #region Lifecycle Methods
-    DateTime GetLastCleanup();
-    void SetLastCleanup(DateTime datetime);
-    #endregion
-
-    #region Downtime Methods
-    DT.Downtime GetDowntime(Guid id);
-    IEnumerable<DT.Downtime> GetDowntimes(Expression<Func<Downtime, bool>> predicate);
-    Guid AddDowntime(DT.Downtime dto);
-    void UpdateDowntime(DT.Downtime dto);
-    void DeleteDowntime(Guid id);
-    #endregion
-
-    #region Statistics Methods
-    DT.Statistics GetStatistic(Guid id);
-    IEnumerable<DT.Statistics> GetStatistics(Expression<Func<Statistics, bool>> predicate);
-    Guid AddStatistics(DT.Statistics dto);
-    void DeleteStatistics(Guid id);
-    List<DT.UserStatistics> GetUserStatistics();
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs	(revision 12878)
@@ -0,0 +1,68 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using HeuristicLab.Services.Hive.DataAccess.Daos;
+using HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics;
+using HeuristicLab.Services.Hive.DataAccess.Data;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Interfaces {
+  public interface IPersistenceManager : IDisposable {
+    DataContext DataContext { get; }
+
+    #region Hive daos
+    AssignedResourceDao AssignedResourceDao { get; }
+    DowntimeDao DowntimeDao { get; }
+    JobDao JobDao { get; }
+    JobPermissionDao JobPermissionDao { get; }
+    LifecycleDao LifecycleDao { get; }
+    PluginDao PluginDao { get; }
+    PluginDataDao PluginDataDao { get; }
+    RequiredPluginDao RequiredPluginDao { get; }
+    ResourceDao ResourceDao { get; }
+    ResourcePermissionDao ResourcePermissionDao { get; }
+    SlaveDao SlaveDao { get; }
+    SlaveGroupDao SlaveGroupDao { get; }
+    StateLogDao StateLogDao { get; }
+    TaskDao TaskDao { get; }
+    TaskDataDao TaskDataDao { get; }
+    UserPriorityDao UserPriorityDao { get; }
+    #endregion
+
+    #region HiveStatistics daos
+    DimClientDao DimClientDao { get; }
+    DimJobDao DimJobDao { get; }
+    DimTimeDao DimTimeDao { get; }
+    DimUserDao DimUserDao { get; }
+    FactClientInfoDao FactClientInfoDao { get; }
+    FactTaskDao FactTaskDao { get; }
+    #endregion
+
+    #region Transaction management
+    void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false);
+    T UseTransaction<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false);
+    #endregion
+
+    TableInformation GetTableInformation(string table);
+    void SubmitChanges();
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs	(revision 12878)
@@ -0,0 +1,239 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Data.Linq;
+using System.Linq;
+using System.Transactions;
+using HeuristicLab.Services.Hive.DataAccess.Daos;
+using HeuristicLab.Services.Hive.DataAccess.Daos.HiveStatistics;
+using HeuristicLab.Services.Hive.DataAccess.Data;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+
+namespace HeuristicLab.Services.Hive.DataAccess.Manager {
+  public class PersistenceManager : IPersistenceManager {
+    private readonly DataContext dataContext;
+    public DataContext DataContext {
+      get { return dataContext; }
+    }
+
+    #region Hive daos
+
+    private AssignedResourceDao assignedResourceDao;
+    public AssignedResourceDao AssignedResourceDao {
+      get { return assignedResourceDao ?? (assignedResourceDao = new AssignedResourceDao(dataContext)); }
+    }
+
+    private DowntimeDao downtimeDao;
+    public DowntimeDao DowntimeDao {
+      get { return downtimeDao ?? (downtimeDao = new DowntimeDao(dataContext)); }
+    }
+
+    private JobDao jobDao;
+    public JobDao JobDao {
+      get { return jobDao ?? (jobDao = new JobDao(dataContext)); }
+    }
+
+    private JobPermissionDao jobPermissionDao;
+    public JobPermissionDao JobPermissionDao {
+      get { return jobPermissionDao ?? (jobPermissionDao = new JobPermissionDao(dataContext)); }
+    }
+
+    private LifecycleDao lifecycleDao;
+    public LifecycleDao LifecycleDao {
+      get { return lifecycleDao ?? (lifecycleDao = new LifecycleDao(dataContext)); }
+    }
+
+    private PluginDao pluginDao;
+    public PluginDao PluginDao {
+      get { return pluginDao ?? (pluginDao = new PluginDao(dataContext)); }
+    }
+
+    private PluginDataDao pluginDataDao;
+    public PluginDataDao PluginDataDao {
+      get { return pluginDataDao ?? (pluginDataDao = new PluginDataDao(dataContext)); }
+    }
+
+    private RequiredPluginDao requiredPluginDao;
+    public RequiredPluginDao RequiredPluginDao {
+      get { return requiredPluginDao ?? (requiredPluginDao = new RequiredPluginDao(dataContext)); }
+    }
+
+    private ResourceDao resourceDao;
+    public ResourceDao ResourceDao {
+      get { return resourceDao ?? (resourceDao = new ResourceDao(dataContext)); }
+    }
+
+    private ResourcePermissionDao resourcePermissionDao;
+    public ResourcePermissionDao ResourcePermissionDao {
+      get { return resourcePermissionDao ?? (resourcePermissionDao = new ResourcePermissionDao(dataContext)); }
+    }
+
+    private SlaveDao slaveDao;
+    public SlaveDao SlaveDao {
+      get { return slaveDao ?? (slaveDao = new SlaveDao(dataContext)); }
+    }
+
+    private SlaveGroupDao slaveGroupDao;
+    public SlaveGroupDao SlaveGroupDao {
+      get { return slaveGroupDao ?? (slaveGroupDao = new SlaveGroupDao(dataContext)); }
+    }
+
+    private StateLogDao stateLogDao;
+    public StateLogDao StateLogDao {
+      get { return stateLogDao ?? (stateLogDao = new StateLogDao(dataContext)); }
+    }
+
+    private TaskDao taskDao;
+    public TaskDao TaskDao {
+      get { return taskDao ?? (taskDao = new TaskDao(dataContext)); }
+    }
+
+    private TaskDataDao taskDataDao;
+    public TaskDataDao TaskDataDao {
+      get { return taskDataDao ?? (taskDataDao = new TaskDataDao(dataContext)); }
+    }
+
+    private UserPriorityDao userPriorityDao;
+    public UserPriorityDao UserPriorityDao {
+      get { return userPriorityDao ?? (userPriorityDao = new UserPriorityDao(dataContext)); }
+    }
+    #endregion
+
+    #region HiveStatistics daos
+
+    private DimClientDao dimClientDao;
+    public DimClientDao DimClientDao {
+      get { return dimClientDao ?? (dimClientDao = new DimClientDao(dataContext)); }
+    }
+
+    private DimJobDao dimJobDao;
+    public DimJobDao DimJobDao {
+      get { return dimJobDao ?? (dimJobDao = new DimJobDao(dataContext)); }
+    }
+
+    private DimTimeDao dimTimeDao;
+    public DimTimeDao DimTimeDao {
+      get { return dimTimeDao ?? (dimTimeDao = new DimTimeDao(dataContext)); }
+    }
+
+    private DimUserDao dimUserDao;
+    public DimUserDao DimUserDao {
+      get { return dimUserDao ?? (dimUserDao = new DimUserDao(dataContext)); }
+    }
+
+    private FactClientInfoDao factClientInfoDao;
+    public FactClientInfoDao FactClientInfoDao {
+      get { return factClientInfoDao ?? (factClientInfoDao = new FactClientInfoDao(dataContext)); }
+    }
+
+    private FactTaskDao factTaskDao;
+    public FactTaskDao FactTaskDao {
+      get { return factTaskDao ?? (factTaskDao = new FactTaskDao(dataContext)); }
+    }
+    #endregion
+
+    public PersistenceManager(bool longRunning = false) {
+      var context = new HiveDataContext(Settings.Default.HeuristicLab_Hive_LinqConnectionString);
+      if (longRunning) context.CommandTimeout = (int)Settings.Default.LongRunningDatabaseCommandTimeout.TotalSeconds;
+      dataContext = context;
+    }
+
+    public PersistenceManager(DataContext dataContext) {
+      this.dataContext = dataContext;
+    }
+
+    #region Transaction management
+    public void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false) {
+      UseTransaction<object>(() => {
+        call();
+        return null;
+      });
+    }
+
+    public T UseTransaction<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false) {
+      int n = 10;
+      while (n > 0) {
+        TransactionScope transaction = CreateTransaction(repeatableRead, longRunning);
+        try {
+          T result = call();
+          transaction.Complete();
+          return result;
+        }
+        catch (System.Data.SqlClient.SqlException e) {
+          n--; // probably deadlock situation, let it roll back and repeat the transaction n times
+          LogFactory.GetLogger(typeof(TransactionManager).Namespace).Log(string.Format("Exception occured, repeating transaction {0} more times. Details: {1}", n, e.ToString()));
+          if (n <= 0) throw;
+        }
+        finally {
+          transaction.Dispose();
+        }
+      }
+      throw new Exception("Transaction couldn't be completed.");
+    }
+
+    private static TransactionScope CreateTransaction(bool repeatableRead, bool longRunning) {
+      var options = new TransactionOptions {
+        IsolationLevel = repeatableRead ? IsolationLevel.RepeatableRead : IsolationLevel.ReadUncommitted
+      };
+      if (longRunning) {
+        options.Timeout = Settings.Default.LongRunningDatabaseCommandTimeout;
+      }
+      return new TransactionScope(TransactionScopeOption.Required, options);
+    }
+    #endregion
+
+    public TableInformation GetTableInformation(string table) {
+      string query = string.Format("sp_spaceused '{0}', @updateusage='true'", table);
+      var result = dataContext.ExecuteQuery<SqlServerTableInformation>(query).FirstOrDefault();
+      if (result == null) return null;
+      return new TableInformation {
+        Name = result.Name,
+        Rows = int.Parse(result.Rows.Remove(result.Rows.IndexOf(' '))),
+        Reserved = int.Parse(result.Reserved.Remove(result.Reserved.IndexOf(' '))),
+        Data = int.Parse(result.Data.Remove(result.Data.IndexOf(' '))),
+        IndexSize = int.Parse(result.Index_Size.Remove(result.Index_Size.IndexOf(' '))),
+        Unused = int.Parse(result.Unused.Remove(result.Unused.IndexOf(' ')))
+      };
+    }
+
+    public void SubmitChanges() {
+      if (dataContext != null) {
+        dataContext.SubmitChanges();
+      }
+    }
+
+    public void Dispose() {
+      if (dataContext != null) {
+        dataContext.Dispose();
+      }
+    }
+
+    private class SqlServerTableInformation {
+      public string Name { get; set; }
+      public string Rows { get; set; }
+      public string Reserved { get; set; }
+      public string Data { get; set; }
+      public string Index_Size { get; set; } // naming of sp_spaceused...
+      public string Unused { get; set; }
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql	(revision 12878)
@@ -1,4 +1,21 @@
-﻿USE [HeuristicLab.Hive-3.3]
-/* create and initialize hive database tables */
+﻿/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+USE [HeuristicLab.Hive-3.3]
 
 EXEC sp_configure filestream_access_level, 2
@@ -131,36 +148,4 @@
   [DateEnqueued] DateTime NOT NULL,
   CONSTRAINT [PK_UserPriority] PRIMARY KEY ([UserId])
-  )
-CREATE TABLE [DeletedJobStatistics](
-  [UserId] UniqueIdentifier NOT NULL,
-  [ExecutionTimeS] float NOT NULL,
-  [ExecutionTimeSFinishedJobs] float NOT NULL,
-  [StartToEndTimeS] float NOT NULL,
-  [DeletedJobStatisticsId] UniqueIdentifier NOT NULL,
-  CONSTRAINT [PK_DeletedJobStatistics] PRIMARY KEY ([DeletedJobStatisticsId])
-  )
-CREATE TABLE [UserStatistics](
-  [StatisticsId] UniqueIdentifier NOT NULL,
-  [UserId] UniqueIdentifier NOT NULL,
-  [ExecutionTimeMs] float NOT NULL,
-  [UsedCores] Int NOT NULL,
-  [ExecutionTimeMsFinishedJobs] float NOT NULL,
-  [StartToEndTimeMs] float NOT NULL,
-  CONSTRAINT [PK_UserStatistics] PRIMARY KEY ([StatisticsId], [UserId])
-  )
-CREATE TABLE [SlaveStatistics](
-  [StatisticsId] UniqueIdentifier NOT NULL,
-  [SlaveId] UniqueIdentifier NOT NULL,
-  [Cores] Int NOT NULL,
-  [FreeCores] Int NOT NULL,
-  [CpuUtilization] float NOT NULL,
-  [Memory] Int NOT NULL,
-  [FreeMemory] Int NOT NULL,
-  CONSTRAINT [PK_SlaveStatistics] PRIMARY KEY ([StatisticsId], [SlaveId])
-  )
-CREATE TABLE [Statistics](
-  [StatisticsId] UniqueIdentifier NOT NULL,
-  [Timestamp] DateTime NOT NULL,
-  CONSTRAINT [PK_Statistics] PRIMARY KEY ([StatisticsId])
   )
 ALTER TABLE [dbo].[AssignedResources]
@@ -192,6 +177,87 @@
 ALTER TABLE [dbo].[JobPermission]
   ADD CONSTRAINT [Job_JobPermission] FOREIGN KEY ([JobId]) REFERENCES [dbo].[Job]([JobId])
-ALTER TABLE [UserStatistics]
-  ADD CONSTRAINT [Statistics_UserStatistics] FOREIGN KEY ([StatisticsId]) REFERENCES [Statistics]([StatisticsId])
-ALTER TABLE [SlaveStatistics]
-  ADD CONSTRAINT [Statistics_SlaveStatistics] FOREIGN KEY ([StatisticsId]) REFERENCES [Statistics]([StatisticsId])
+
+GO
+CREATE SCHEMA [statistics]
+GO
+
+CREATE TABLE [statistics].[DimTime] (
+    [Time]   DATETIME NOT NULL,
+    [Minute] DATETIME NOT NULL,
+    [Hour]   DATETIME NOT NULL,
+    [Day]    DATE     NOT NULL,
+    [Month]  DATE     NOT NULL,
+    [Year]   DATE     NOT NULL,
+    CONSTRAINT [PK_DimTime] PRIMARY KEY CLUSTERED ([Time] ASC)
+);
+CREATE TABLE [statistics].[DimClient] (
+    [Id]               UNIQUEIDENTIFIER CONSTRAINT [DF_DimClient_Id] DEFAULT (newsequentialid()) NOT NULL,
+    [Name]             VARCHAR (MAX)    NOT NULL,
+    [ResourceId]       UNIQUEIDENTIFIER NOT NULL,
+    [ExpirationTime]   DATETIME         NULL,
+    [ResourceGroupId]  UNIQUEIDENTIFIER NULL,
+    [ResourceGroup2Id] UNIQUEIDENTIFIER NULL,
+    [GroupName]        VARCHAR (MAX)    NULL,
+    [GroupName2]       VARCHAR (MAX)    NULL,
+    CONSTRAINT [PK_DimClient] PRIMARY KEY CLUSTERED ([Id] ASC)
+);
+CREATE TABLE [statistics].[DimJob] (
+    [JobId]          UNIQUEIDENTIFIER NOT NULL,
+    [UserId]         UNIQUEIDENTIFIER NOT NULL,
+    [JobName]        VARCHAR (MAX)    NOT NULL,
+    [UserName]       VARCHAR (MAX)    NOT NULL,
+    [DateCreated]    DATETIME		  NOT NULL,
+    [TotalTasks]     INT              NOT NULL,
+    [CompletedTasks] INT              NOT NULL,
+    [DateCompleted]  DATETIME		  NULL,
+    CONSTRAINT [PK_DimJob] PRIMARY KEY CLUSTERED ([JobId] ASC)
+);
+CREATE TABLE [statistics].[DimUser] (
+    [UserId] UNIQUEIDENTIFIER NOT NULL,
+    [Name]   VARCHAR (MAX)    NOT NULL,
+    CONSTRAINT [PK_DimUser] PRIMARY KEY CLUSTERED ([UserId] ASC)
+);
+CREATE TABLE [statistics].[FactClientInfo] (
+    [ClientId]             UNIQUEIDENTIFIER NOT NULL,
+    [Time]                 DATETIME         NOT NULL,
+    [UserId]               UNIQUEIDENTIFIER NOT NULL,
+    [NumUsedCores]         INT              NOT NULL,
+    [NumTotalCores]        INT              NOT NULL,
+    [UsedMemory]           INT              NOT NULL,
+    [TotalMemory]          INT              NOT NULL,
+    [CpuUtilization]       FLOAT (53)       NOT NULL,
+    [SlaveState]           VarChar(15)      NOT NULL,
+    [IdleTime]             INT              NOT NULL,
+    [OfflineTime]          INT              NOT NULL,
+    [UnavailableTime]      INT              NOT NULL,
+    [IsAllowedToCalculate] BIT              NOT NULL,
+    CONSTRAINT [PK_FactClientInfo] PRIMARY KEY CLUSTERED ([ClientId] ASC, [Time] ASC, [UserId] ASC),
+    CONSTRAINT [FK_FactClientInfo_DimTime] FOREIGN KEY ([Time]) REFERENCES [statistics].[DimTime] ([Time]),
+    CONSTRAINT [FK_FactClientInfo_DimClient] FOREIGN KEY ([ClientId]) REFERENCES [statistics].[DimClient] ([Id]),
+    CONSTRAINT [FK_FactClientInfo_DimUser] FOREIGN KEY ([UserId]) REFERENCES [statistics].[DimUser] ([UserId])
+);
+CREATE TABLE [statistics].[FactTask] (
+  [TaskId]             UNIQUEIDENTIFIER NOT NULL,
+  [CalculatingTime]    INT              NOT NULL,
+  [WaitingTime]        INT              NOT NULL,
+  [TransferTime]       INT              NOT NULL,
+  [NumCalculationRuns] INT              NOT NULL,
+  [NumRetries]         INT              NOT NULL,
+  [CoresRequired]      INT              NOT NULL,
+  [MemoryRequired]     INT              NOT NULL,
+  [Priority]           INT              NOT NULL,
+  [LastClientId]       UNIQUEIDENTIFIER NULL,
+  [JobId]		       UNIQUEIDENTIFIER NOT NULL,
+  [StartTime]          DATETIME         NULL,
+  [EndTime]            DATETIME         NULL,
+  [TaskState]          VARCHAR (30)     NOT NULL,
+  [Exception]          VARCHAR (MAX)    NULL,
+  [InitialWaitingTime] INT              NULL,
+  CONSTRAINT [PK_FactTask] PRIMARY KEY CLUSTERED ([TaskId] ASC),
+  CONSTRAINT [FK_FactTask_DimClient] FOREIGN KEY ([LastClientId]) REFERENCES [statistics].[DimClient] ([Id]),
+  CONSTRAINT [FK_FactTask_DimJob] FOREIGN KEY ([JobId]) REFERENCES [statistics].[DimJob] ([JobId])
+);
+
+/* dummy for nullable userIds in FactClientInfo */
+INSERT INTO [statistics].[DimUser] ([UserId], [Name])
+VALUES ('00000000-0000-0000-0000-000000000000', 'NULL');
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Migrate to Filestream.sql
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Migrate to Filestream.sql	(revision 12877)
+++ 	(revision )
@@ -1,79 +1,0 @@
-/* Upgrade script from HeuristicLab Hive 3.3.8 to 3.3.9 */
-
-USE [HeuristicLab.Hive-3.3]
-
-EXEC sp_configure filestream_access_level, 2
-GO
-RECONFIGURE
-GO 
-
-/**********************************************************/
-
-/* Move old Task Data */
-
-CREATE TABLE [dbo].[TaskDataTemp](
-  [TaskId] UniqueIdentifier RowGuidCol NOT NULL,
-  [Data] VarBinary(MAX) Filestream NOT NULL,
-  [LastUpdate] DateTime NOT NULL,
-  CONSTRAINT [PK_dbo.TaskDataTemp] PRIMARY KEY ([TaskId])
-)
-
-INSERT INTO dbo.TaskDataTemp (TaskId, Data, LastUpdate)
-SELECT TaskId, Data, LastUpdate
-FROM dbo.TaskData
-
-DELETE FROM dbo.TaskData
-
-/* Alter TaskId and Data Column */
-
-ALTER TABLE dbo.TaskData
-ALTER COLUMN [TaskId] ADD RowGuidCol
-
-ALTER TABLE dbo.TaskData
-DROP COLUMN Data
-
-ALTER TABLE dbo.TaskData
-ADD [Data] VarBinary(MAX) Filestream NOT NULL
-
-/* Insert data */
-
-INSERT INTO dbo.TaskData (TaskId, Data, LastUpdate)
-SELECT TaskId, Data, LastUpdate
-FROM dbo.TaskDataTemp
-
-DROP TABLE dbo.TaskDataTemp
-
-
-/**********************************************************/
-
-/* Move old Plugin Data */
-
-CREATE TABLE [dbo].[PluginDataTemp](
-  [PluginDataId] UniqueIdentifier RowGuidCol NOT NULL,
-  [PluginId] UniqueIdentifier NOT NULL,
-  [Data] VarBinary(MAX) FileStream NOT NULL,
-  [FileName] VarChar(MAX) NOT NULL,
-  CONSTRAINT [PK_dbo.PluginDataTemp] PRIMARY KEY ([PluginDataId])
-)
-
-INSERT INTO dbo.PluginDataTemp (PluginDataId, PluginId, Data, [FileName])
-SELECT PluginDataId, PluginId, Data, [FileName]
-FROM dbo.PluginData
-
-DELETE FROM dbo.PluginData
-
-/* Alter Data Column */
-
-ALTER TABLE dbo.PluginData
-DROP COLUMN Data
-
-ALTER TABLE dbo.PluginData
-ADD [Data] VarBinary(MAX) Filestream NOT NULL
-
-/* Insert data */
-
-INSERT INTO dbo.PluginData (PluginDataId, PluginId, Data, [FileName])
-SELECT PluginDataId, PluginId, Data, [FileName]
-FROM dbo.PluginDataTemp
-
-DROP TABLE dbo.PluginDataTemp
Index: /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql	(revision 12878)
@@ -1,10 +1,23 @@
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* this script is supposed to be executed after the plain DB is generated by the linq-to-sql schema */
 USE [HeuristicLab.Hive-3.3]
-/* this script is supposed to be executed after the plain DB is generated by the linq-to-sql schema */
-/* adds default values */
-/* creates delete and update cascades */
-/* creates indices */
-/* creates views */
-/* creates triggers */
-
 
 ALTER TABLE [dbo].[AssignedResources]  DROP  CONSTRAINT [Task_AssignedResource]
@@ -93,78 +106,21 @@
 GO
 
-ALTER TABLE [dbo].[Statistics] ALTER COLUMN StatisticsId ADD ROWGUIDCOL;
-ALTER TABLE [dbo].[Statistics] WITH NOCHECK ADD CONSTRAINT [DF_Statistics_StatisticsId] DEFAULT (NEWSEQUENTIALID()) FOR StatisticsId;
-GO
-
-ALTER TABLE [dbo].[DeletedJobStatistics] ALTER COLUMN DeletedJobStatisticsId ADD ROWGUIDCOL;
-ALTER TABLE [dbo].[DeletedJobStatistics] WITH NOCHECK ADD CONSTRAINT [DF_DeletedJobStatistics_DeletedJobStatisticsId] DEFAULT (NEWSEQUENTIALID()) FOR DeletedJobStatisticsId;
-GO
-
-ALTER TABLE [dbo].[SlaveStatistics]  DROP  CONSTRAINT [Statistics_SlaveStatistics]
-ALTER TABLE [dbo].[SlaveStatistics]  WITH CHECK ADD  CONSTRAINT [Statistics_SlaveStatistics] FOREIGN KEY([StatisticsId])
-REFERENCES [dbo].[Statistics] ([StatisticsId])
-ON UPDATE CASCADE
-ON DELETE CASCADE
-GO
-
-ALTER TABLE [dbo].[UserStatistics]  DROP  CONSTRAINT [Statistics_UserStatistics]
-ALTER TABLE [dbo].[UserStatistics]  WITH CHECK ADD  CONSTRAINT [Statistics_UserStatistics] FOREIGN KEY([StatisticsId])
-REFERENCES [dbo].[Statistics] ([StatisticsId])
-ON UPDATE CASCADE
-ON DELETE CASCADE
-GO
-
 /* create indices */
 CREATE INDEX Index_RequiredPlugins_TaskId ON RequiredPlugins(TaskId);
 GO
 
-/* views */
--- =============================================
--- Author:		cneumuel
--- Description:	Returns the first StateLog entry for each job
--- =============================================
-CREATE VIEW [dbo].[view_FirstState]
-AS
-SELECT     sl.TaskId, sl.DateTime, sl.State
-FROM         dbo.StateLog AS sl INNER JOIN
-                          (SELECT     TaskId, MIN(DateTime) AS DateTime
-                            FROM          dbo.StateLog
-                            GROUP BY TaskId) AS minDate ON sl.DateTime = minDate.DateTime AND sl.TaskId = minDate.TaskId
-
+-- speed up joins between Job and Task
+CREATE NONCLUSTERED INDEX [TaskJobIdIndex]
+ON [dbo].[Task] ([JobId])
+INCLUDE ([TaskId],[TaskState],[ExecutionTimeMs],[LastHeartbeat],[ParentTaskId],[Priority],[CoresNeeded],[MemoryNeeded],[IsParentTask],[FinishWhenChildJobsFinished],[Command],[IsPrivileged])
 GO
 
--- =============================================
--- Author:		cneumuel
--- Description:	Returns the last StateLog entry for each job
--- =============================================
-CREATE VIEW [dbo].[view_LastState]
-AS
-SELECT     sl.TaskId, sl.DateTime, sl.State
-FROM         dbo.StateLog AS sl INNER JOIN
-                          (SELECT     TaskId, MAX(DateTime) AS DateTime
-                            FROM          dbo.StateLog
-                            GROUP BY TaskId) AS minDate ON sl.DateTime = minDate.DateTime AND sl.TaskId = minDate.TaskId
+-- this is an index to speed up the GetWaitingTasks() method 
+CREATE NONCLUSTERED INDEX [TaskGetWaitingTasksIndex]
+ON [dbo].[Task] ([TaskState],[IsParentTask],[FinishWhenChildJobsFinished],[CoresNeeded],[MemoryNeeded])
+INCLUDE ([TaskId],[ExecutionTimeMs],[LastHeartbeat],[ParentTaskId],[Priority],[Command],[JobId],[IsPrivileged])
 GO
 
--- =============================================
--- Author:		cneumuel
--- Description:	returns aggregates statistic information for every minute
--- =============================================
-CREATE VIEW [dbo].[view_Statistics]
-AS
-SELECT  CONVERT(VARCHAR(19), MIN(s.Timestamp), 120) AS DateTime, SUM(ss.Cores) AS Cores, SUM(ss.FreeCores) AS FreeCores, 
-        AVG(ss.CpuUtilization) AS CpuUtilization, SUM(ss.Memory) AS Memory, SUM(ss.FreeMemory) AS FreeMemory, x.exSum AS ExecutionTimeHours, 
-        x.exFinishedSum AS ExecutionTimeFinished, x.exStartToEndSum AS StartToEndTimeFinished
-FROM         dbo.SlaveStatistics AS ss INNER JOIN
-                      dbo.[Statistics] AS s ON ss.StatisticsId = s.StatisticsId INNER JOIN
-                      dbo.Resource AS r ON ss.SlaveId = r.ResourceId INNER JOIN
-                          (SELECT     StatisticsId, SUM(ExecutionTimeMs) / 1000 / 60 / 60 AS exSum, SUM(ExecutionTimeMsFinishedJobs) / 1000 / 60 / 60 AS exFinishedSum, 
-                                                   SUM(StartToEndTimeMs) / 1000 / 60 / 60 AS exStartToEndSum
-                            FROM          dbo.UserStatistics AS us
-                            GROUP BY StatisticsId) AS x ON s.StatisticsId = x.StatisticsId
-GROUP BY s.StatisticsId, x.exSum, x.exFinishedSum, x.exStartToEndSum
 
-/* triggers */
-GO
 /****** Object:  Trigger [dbo].[tr_JobDeleteCascade]    Script Date: 04/19/2011 16:31:53 ******/
 SET ANSI_NULLS ON
@@ -182,6 +138,6 @@
 CREATE TRIGGER [dbo].[tr_JobDeleteCascade] ON [dbo].[Job] INSTEAD OF DELETE AS 
 BEGIN
-	DELETE Task FROM deleted, Task WHERE deleted.JobId = Task.JobId
-	DELETE Job FROM deleted, Job WHERE deleted.JobId = Job.JobId
+    DELETE Task FROM deleted, Task WHERE deleted.JobId = Task.JobId
+    DELETE Job FROM deleted, Job WHERE deleted.JobId = Job.JobId
 END
 GO
@@ -191,74 +147,32 @@
 -- Create date: 11.11.2010
 -- Description:	Recursively deletes all child-jobs of a job when it is deleted. (Source: http://devio.wordpress.com/2008/05/23/recursive-delete-in-sql-server/)
--- =============================================
+-- =============================================DeletedJobStatistics
 CREATE TRIGGER [dbo].[tr_TaskDeleteCascade] ON [dbo].[Task] INSTEAD OF DELETE AS 
 BEGIN
-	-- add statistics
-	INSERT INTO dbo.DeletedJobStatistics (UserId, ExecutionTimeS, ExecutionTimeSFinishedJobs, StartToEndTimeS)
-	SELECT 
-		he.OwnerUserId AS UserId, 
-		ROUND(SUM(j.ExecutionTimeMs) / 1000, 0) AS ExecutionTimeS,  
-		ROUND(ISNULL(SUM(CASE ls.State WHEN 'Finished' THEN j.ExecutionTimeMs END), 0) / 1000, 0) AS ExecutionTimeSFinishedJobs,
-		ISNULL(SUM(CASE ls.State WHEN 'Finished' THEN DATEDIFF(s, fs.DateTime, ls.DateTime) ELSE 0 END), 0) AS StartToEndTimeS
-	FROM 
-		deleted j, 
-		Job he, 
-		view_FirstState fs, 
-		view_LastState ls
-	WHERE 
-		he.JobId = j.JobId AND 
-		fs.TaskId = j.TaskId AND 
-		ls.TaskId = j.TaskId
-	GROUP BY he.OwnerUserId
-
-	-- recursively delete jobs
-	CREATE TABLE #Table( 
-		TaskId uniqueidentifier 
-	)
-	INSERT INTO #Table (TaskId)
-	SELECT TaskId FROM deleted
-	
-	DECLARE @c INT
-	SET @c = 0
-	
-	WHILE @c <> (SELECT COUNT(TaskId) FROM #Table) BEGIN
-		SELECT @c = COUNT(TaskId) FROM #Table
-		
-		INSERT INTO #Table (TaskId)
-			SELECT Task.TaskId
-			FROM Task
-			LEFT OUTER JOIN #Table ON Task.TaskId = #Table.TaskId
-			WHERE Task.ParentTaskId IN (SELECT TaskId FROM #Table)
-				AND #Table.TaskId IS NULL
-	END
-	
-	DELETE TaskData FROM TaskData INNER JOIN #Table ON TaskData.TaskId = #Table.TaskId
-	DELETE Task FROM Task INNER JOIN #Table ON Task.TaskId = #Table.TaskId
+    -- recursively delete jobs
+    CREATE TABLE #Table( 
+        TaskId uniqueidentifier 
+    )
+    INSERT INTO #Table (TaskId)
+    SELECT TaskId FROM deleted
+    
+    DECLARE @c INT
+    SET @c = 0
+    
+    WHILE @c <> (SELECT COUNT(TaskId) FROM #Table) BEGIN
+        SELECT @c = COUNT(TaskId) FROM #Table
+        
+        INSERT INTO #Table (TaskId)
+            SELECT Task.TaskId
+            FROM Task
+            LEFT OUTER JOIN #Table ON Task.TaskId = #Table.TaskId
+            WHERE Task.ParentTaskId IN (SELECT TaskId FROM #Table)
+                AND #Table.TaskId IS NULL
+    END
+    
+    DELETE TaskData FROM TaskData INNER JOIN #Table ON TaskData.TaskId = #Table.TaskId
+    DELETE Task FROM Task INNER JOIN #Table ON Task.TaskId = #Table.TaskId
 END
 GO
 
 
-CREATE TRIGGER [dbo].[tr_StatisticsDeleteCascade] ON [dbo].[Statistics] INSTEAD OF DELETE AS 
-BEGIN
-	DELETE SlaveStatistics FROM deleted, SlaveStatistics WHERE deleted.StatisticsId = SlaveStatistics.StatisticsId
-	-- should also remove UserStatistics here 
-	DELETE [Statistics] FROM deleted, [Statistics] WHERE deleted.StatisticsId = [Statistics].StatisticsId
-END
-GO
-
-
--- ============================================================
--- Description:	Create indices to speed up execution of queries
--- ============================================================
-
--- speed up joins between Job and Task
-CREATE NONCLUSTERED INDEX [TaskJobIdIndex]
-ON [dbo].[Task] ([JobId])
-INCLUDE ([TaskId],[TaskState],[ExecutionTimeMs],[LastHeartbeat],[ParentTaskId],[Priority],[CoresNeeded],[MemoryNeeded],[IsParentTask],[FinishWhenChildJobsFinished],[Command],[IsPrivileged])
-GO
-
--- this is an index to speed up the GetWaitingTasks() method 
-CREATE NONCLUSTERED INDEX [TaskGetWaitingTasksIndex]
-ON [dbo].[Task] ([TaskState],[IsParentTask],[FinishWhenChildJobsFinished],[CoresNeeded],[MemoryNeeded])
-INCLUDE ([TaskId],[ExecutionTimeMs],[LastHeartbeat],[ParentTaskId],[Priority],[Command],[JobId],[IsPrivileged])
-GO
Index: unk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/TransactionManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.DataAccess/3.3/TransactionManager.cs	(revision 12877)
+++ 	(revision )
@@ -1,82 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Transactions;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public class TransactionManager : ITransactionManager {
-    public void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false) {
-      int n = 10;
-      while (n > 0) {
-        TransactionScope transaction = CreateTransaction(repeatableRead, longRunning);
-        try {
-          call();
-          transaction.Complete();
-          n = 0;
-        }
-        catch (System.Data.SqlClient.SqlException e) {
-          n--; // probably deadlock situation, let it roll back and repeat the transaction n times
-          LogFactory.GetLogger(typeof(TransactionManager).Namespace).Log(string.Format("Exception occured, repeating transaction {0} more times. Details: {1}", n, e.ToString()));
-          if (n <= 0) throw;
-        }
-        finally {
-          transaction.Dispose();
-        }
-      }
-    }
-
-    public T UseTransaction<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false) {
-      int n = 10;
-      while (n > 0) {
-        TransactionScope transaction = CreateTransaction(repeatableRead, longRunning);
-        try {
-          T result = call();
-          transaction.Complete();
-          n = 0;
-          return result;
-        }
-        catch (System.Data.SqlClient.SqlException e) {
-          n--; // probably deadlock situation, let it roll back and repeat the transaction n times
-          LogFactory.GetLogger(typeof(TransactionManager).Namespace).Log(string.Format("Exception occured, repeating transaction {0} more times. Details: {1}", n, e.ToString()));
-          if (n <= 0) throw;
-        }
-        finally {
-          transaction.Dispose();
-        }
-      }
-      throw new Exception("This code should not be reached");
-    }
-
-    private TransactionScope CreateTransaction(bool repeatableRead, bool longRunning) {
-      var options = new TransactionOptions();
-      if (repeatableRead)
-        options.IsolationLevel = IsolationLevel.RepeatableRead;
-      else
-        options.IsolationLevel = IsolationLevel.ReadUncommitted;
-
-      if (longRunning)
-        options.Timeout = Settings.Default.LongRunningDatabaseCommandTimeout;
-
-      return new TransactionScope(TransactionScopeOption.Required, options);
-    }
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/HeuristicLab.Services.Hive.JanitorService-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/HeuristicLab.Services.Hive.JanitorService-3.3.csproj	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/HeuristicLab.Services.Hive.JanitorService-3.3.csproj	(revision 12878)
@@ -124,4 +124,12 @@
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Access.DataAccess\3.3\HeuristicLab.Services.Access.DataAccess-3.3.csproj">
+      <Project>{0f652437-998a-4eab-8bf1-444b5fe8ce97}</Project>
+      <Name>HeuristicLab.Services.Access.DataAccess-3.3</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Access\3.3\HeuristicLab.Services.Access-3.3.csproj">
+      <Project>{9fac0b23-2730-452a-9ba0-d7ca1746c541}</Project>
+      <Name>HeuristicLab.Services.Access-3.3</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\HeuristicLab.Services.Hive.DataAccess\3.3\HeuristicLab.Services.Hive.DataAccess-3.3.csproj">
       <Project>{EC2C8109-6E1E-4C88-9A2B-908CFF2EF4AC}</Project>
Index: /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs	(revision 12878)
@@ -26,5 +26,6 @@
   public partial class JanitorService : ServiceBase {
     private HiveJanitor janitor;
-    private Thread janitorThread;
+    private Thread janitorCleanupThread;
+    private Thread janitorGenerateStatisticsThread;
 
     public JanitorService() {
@@ -35,12 +36,19 @@
       janitor = new HiveJanitor();
 
-      janitorThread = new Thread(janitor.Run);
-      janitorThread.IsBackground = true; //dont keep app alive
-      janitorThread.Start();
+      janitorCleanupThread = new Thread(janitor.RunCleanup) {
+        IsBackground = true
+      };
+      janitorGenerateStatisticsThread = new Thread(janitor.RunGenerateStatistics) {
+        IsBackground = true
+      };
+
+      janitorCleanupThread.Start();
+      janitorGenerateStatisticsThread.Start();
     }
 
     protected override void OnStop() {
       janitor.StopJanitor();
-      janitorThread.Join();
+      janitorCleanupThread.Join();
+      janitorGenerateStatisticsThread.Join();
     }
   }
Index: unk/sources/HeuristicLab.Services.Hive/3.3/Convert.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Convert.cs	(revision 12877)
+++ 	(revision )
@@ -1,571 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Data.Linq;
-using System.Linq;
-using DB = HeuristicLab.Services.Hive.DataAccess;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataTransfer {
-  public static class Convert {
-    #region Task
-    public static DT.Task ToDto(DB.Task source) {
-      if (source == null) return null;
-      return new DT.Task {
-        Id = source.TaskId,
-        CoresNeeded = source.CoresNeeded,
-        ExecutionTime = TimeSpan.FromMilliseconds(source.ExecutionTimeMs),
-        MemoryNeeded = source.MemoryNeeded,
-        ParentTaskId = source.ParentTaskId,
-        Priority = source.Priority,
-        PluginsNeededIds = (source.RequiredPlugins == null ? new List<Guid>() : source.RequiredPlugins.Select(x => x.PluginId).ToList()),
-        LastHeartbeat = source.LastHeartbeat,
-        State = Convert.ToDto(source.State),
-        StateLog = (source.StateLogs == null ? new List<DT.StateLog>() : source.StateLogs.Select(x => Convert.ToDto(x)).OrderBy(x => x.DateTime).ToList()),
-        IsParentTask = source.IsParentTask,
-        FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished,
-        Command = Convert.ToDto(source.Command),
-        LastTaskDataUpdate = (source.JobData == null ? DateTime.MinValue : source.JobData.LastUpdate),
-        JobId = source.JobId,
-        IsPrivileged = source.IsPrivileged
-      };
-    }
-
-    public static DB.Task ToEntity(DT.Task source) {
-      if (source == null) return null;
-      var entity = new DB.Task(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Task source, DB.Task target) {
-      if ((source != null) && (target != null)) {
-        target.TaskId = source.Id;
-        target.CoresNeeded = source.CoresNeeded;
-        target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
-        target.MemoryNeeded = source.MemoryNeeded;
-        target.ParentTaskId = source.ParentTaskId;
-        target.Priority = source.Priority;
-        target.LastHeartbeat = source.LastHeartbeat;
-        target.State = Convert.ToEntity(source.State);
-        foreach (DT.StateLog sl in source.StateLog.Where(x => x.Id == Guid.Empty)) {
-          target.StateLogs.Add(Convert.ToEntity(sl));
-        }
-        target.IsParentTask = source.IsParentTask;
-        target.FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished;
-        target.Command = Convert.ToEntity(source.Command);
-        // RequiredPlugins are added by Dao
-        target.JobId = source.JobId;
-        target.IsPrivileged = source.IsPrivileged;
-      }
-    }
-
-    public static void ToEntityTaskOnly(DT.Task source, DB.Task target) {
-      if ((source != null) && (target != null)) {
-        target.TaskId = source.Id;
-        target.CoresNeeded = source.CoresNeeded;
-        target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
-        target.MemoryNeeded = source.MemoryNeeded;
-        target.ParentTaskId = source.ParentTaskId;
-        target.Priority = source.Priority;
-        target.LastHeartbeat = source.LastHeartbeat;
-        target.State = Convert.ToEntity(source.State);
-        target.IsParentTask = source.IsParentTask;
-        target.FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished;
-        target.Command = Convert.ToEntity(source.Command);
-        // RequiredPlugins are added by Dao
-        target.JobId = source.JobId;
-        target.IsPrivileged = source.IsPrivileged;
-      }
-    }
-    #endregion
-
-    #region TaskData
-    public static DT.TaskData ToDto(DB.TaskData source) {
-      if (source == null) return null;
-      return new DT.TaskData { TaskId = source.TaskId, Data = source.Data, LastUpdate = source.LastUpdate };
-    }
-    public static DB.TaskData ToEntity(DT.TaskData source) {
-      if (source == null) return null;
-      var entity = new DB.TaskData(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.TaskData source, DB.TaskData target) {
-      if ((source != null) && (target != null)) {
-        target.TaskId = source.TaskId;
-        target.Data = source.Data;
-        target.LastUpdate = source.LastUpdate;
-      }
-    }
-    #endregion
-
-    #region StateLog
-    public static DT.StateLog ToDto(DB.StateLog source) {
-      if (source == null) return null;
-      return new DT.StateLog { Id = source.StateLogId, DateTime = source.DateTime, Exception = source.Exception, TaskId = source.TaskId, SlaveId = source.SlaveId, State = Convert.ToDto(source.State), UserId = source.UserId };
-    }
-    public static DB.StateLog ToEntity(DT.StateLog source) {
-      if (source == null) return null;
-      var entity = new DB.StateLog(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.StateLog source, DB.StateLog target) {
-      if ((source != null) && (target != null)) {
-        target.StateLogId = source.Id; target.DateTime = source.DateTime; target.Exception = source.Exception; target.TaskId = source.TaskId; target.SlaveId = source.SlaveId; target.State = Convert.ToEntity(source.State); target.UserId = source.UserId;
-      }
-    }
-    #endregion
-
-    #region Downtimes
-    public static DT.Downtime ToDto(DB.Downtime source) {
-      if (source == null) return null;
-      return new DT.Downtime { Id = source.DowntimeId, AllDayEvent = source.AllDayEvent, EndDate = source.EndDate, Recurring = source.Recurring, RecurringId = source.RecurringId, ResourceId = source.ResourceId, StartDate = source.StartDate, DowntimeType = source.DowntimeType };
-    }
-    public static DB.Downtime ToEntity(DT.Downtime source) {
-      if (source == null) return null;
-      var entity = new DB.Downtime(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Downtime source, DB.Downtime target) {
-      if ((source != null) && (target != null)) {
-        target.DowntimeId = source.Id; target.AllDayEvent = source.AllDayEvent; target.EndDate = source.EndDate; target.Recurring = source.Recurring; target.RecurringId = source.RecurringId; target.ResourceId = source.ResourceId; target.StartDate = source.StartDate; target.DowntimeType = source.DowntimeType;
-      }
-    }
-    #endregion
-
-    #region Job
-    public static DT.Job ToDto(DB.Job source) {
-      if (source == null) return null;
-      return new DT.Job { Id = source.JobId, Description = source.Description, Name = source.Name, OwnerUserId = source.OwnerUserId, DateCreated = source.DateCreated, ResourceNames = source.ResourceIds };
-    }
-    public static DB.Job ToEntity(DT.Job source) {
-      if (source == null) return null;
-      var entity = new DB.Job(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Job source, DB.Job target) {
-      if ((source != null) && (target != null)) {
-        target.JobId = source.Id; target.Description = source.Description; target.Name = source.Name; target.OwnerUserId = source.OwnerUserId; target.DateCreated = source.DateCreated; target.ResourceIds = source.ResourceNames;
-      }
-    }
-    #endregion
-
-    #region JobPermission
-    public static DT.JobPermission ToDto(DB.JobPermission source) {
-      if (source == null) return null;
-      return new DT.JobPermission { JobId = source.JobId, GrantedUserId = source.GrantedUserId, GrantedByUserId = source.GrantedByUserId, Permission = Convert.ToDto(source.Permission) };
-    }
-    public static DB.JobPermission ToEntity(DT.JobPermission source) {
-      if (source == null) return null;
-      var entity = new DB.JobPermission(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.JobPermission source, DB.JobPermission target) {
-      if ((source != null) && (target != null)) {
-        target.JobId = source.JobId; target.GrantedUserId = source.GrantedUserId; target.GrantedByUserId = source.GrantedByUserId; target.Permission = Convert.ToEntity(source.Permission);
-      }
-    }
-    #endregion
-
-    #region Plugin
-    public static DT.Plugin ToDto(DB.Plugin source) {
-      if (source == null) return null;
-      return new DT.Plugin { Id = source.PluginId, Name = source.Name, Version = new Version(source.Version), UserId = source.UserId, DateCreated = source.DateCreated, Hash = source.Hash };
-    }
-    public static DB.Plugin ToEntity(DT.Plugin source) {
-      if (source == null) return null;
-      var entity = new DB.Plugin(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Plugin source, DB.Plugin target) {
-      if ((source != null) && (target != null)) {
-        target.PluginId = source.Id; target.Name = source.Name; target.Version = source.Version.ToString(); target.UserId = source.UserId; target.DateCreated = source.DateCreated; target.Hash = source.Hash;
-      }
-    }
-    #endregion
-
-    #region PluginData
-    public static DT.PluginData ToDto(DB.PluginData source) {
-      if (source == null) return null;
-      return new DT.PluginData { Id = source.PluginDataId, PluginId = source.PluginId, Data = source.Data.ToArray(), FileName = source.FileName };
-    }
-    public static DB.PluginData ToEntity(DT.PluginData source) {
-      if (source == null) return null;
-      var entity = new DB.PluginData(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.PluginData source, DB.PluginData target) {
-      if ((source != null) && (target != null)) {
-        target.PluginDataId = source.Id;
-        target.PluginId = source.PluginId;
-        target.Data = source.Data;
-        target.FileName = source.FileName;
-      }
-    }
-    #endregion
-
-    #region Resource
-    public static DT.Resource ToDto(DB.Resource source) {
-      if (source == null) return null;
-      return new DT.Resource { Id = source.ResourceId, Name = source.Name, ParentResourceId = source.ParentResourceId, HbInterval = source.HbInterval, OwnerUserId = source.OwnerUserId };
-    }
-    public static DB.Resource ToEntity(DT.Resource source) {
-      if (source == null) return null;
-      var entity = new DB.Resource(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Resource source, DB.Resource target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id; target.Name = source.Name; target.ParentResourceId = source.ParentResourceId; target.HbInterval = source.HbInterval; target.OwnerUserId = source.OwnerUserId;
-      }
-    }
-    #endregion
-
-    #region SlaveGroup
-    public static DT.SlaveGroup ToDto(DB.SlaveGroup source) {
-      if (source == null) return null;
-      return new DT.SlaveGroup { Id = source.ResourceId, Name = source.Name, ParentResourceId = source.ParentResourceId, HbInterval = source.HbInterval, OwnerUserId = source.OwnerUserId };
-    }
-    public static DB.SlaveGroup ToEntity(DT.SlaveGroup source) {
-      if (source == null) return null;
-      var entity = new DB.SlaveGroup(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.SlaveGroup source, DB.SlaveGroup target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id; target.Name = source.Name; target.ParentResourceId = source.ParentResourceId; target.HbInterval = source.HbInterval; target.OwnerUserId = source.OwnerUserId;
-      }
-    }
-    #endregion
-
-    #region Slave
-    public static DT.Slave ToDto(DB.Slave source) {
-      if (source == null) return null;
-      return new DT.Slave {
-        Id = source.ResourceId,
-        ParentResourceId = source.ParentResourceId,
-        Cores = source.Cores,
-        CpuSpeed = source.CpuSpeed,
-        FreeCores = source.FreeCores,
-        FreeMemory = source.FreeMemory,
-        IsAllowedToCalculate = source.IsAllowedToCalculate,
-        Memory = source.Memory,
-        Name = source.Name,
-        SlaveState = Convert.ToDto(source.SlaveState),
-        CpuArchitecture = Convert.ToDto(source.CpuArchitecture),
-        OperatingSystem = source.OperatingSystem,
-        LastHeartbeat = source.LastHeartbeat,
-        CpuUtilization = source.CpuUtilization,
-        HbInterval = source.HbInterval,
-        IsDisposable = source.IsDisposable,
-        OwnerUserId = source.OwnerUserId
-      };
-    }
-    public static DB.Slave ToEntity(DT.Slave source) {
-      if (source == null) return null;
-      var entity = new DB.Slave(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Slave source, DB.Slave target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.Id;
-        target.ParentResourceId = source.ParentResourceId;
-        target.Cores = source.Cores;
-        target.CpuSpeed = source.CpuSpeed;
-        target.FreeCores = source.FreeCores;
-        target.FreeMemory = source.FreeMemory;
-        target.IsAllowedToCalculate = source.IsAllowedToCalculate;
-        target.Memory = source.Memory;
-        target.Name = source.Name;
-        target.SlaveState = Convert.ToEntity(source.SlaveState);
-        target.CpuArchitecture = Convert.ToEntity(source.CpuArchitecture);
-        target.OperatingSystem = source.OperatingSystem;
-        target.LastHeartbeat = source.LastHeartbeat;
-        target.CpuUtilization = source.CpuUtilization;
-        target.HbInterval = source.HbInterval;
-        target.IsDisposable = source.IsDisposable;
-        target.OwnerUserId = source.OwnerUserId;
-      }
-    }
-    #endregion
-
-    #region ResourcePermission
-    public static DT.ResourcePermission ToDto(DB.ResourcePermission source) {
-      if (source == null) return null;
-      return new DT.ResourcePermission { ResourceId = source.ResourceId, GrantedUserId = source.GrantedUserId, GrantedByUserId = source.GrantedByUserId };
-    }
-    public static DB.ResourcePermission ToEntity(DT.ResourcePermission source) {
-      if (source == null) return null;
-      var entity = new DB.ResourcePermission(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.ResourcePermission source, DB.ResourcePermission target) {
-      if ((source != null) && (target != null)) {
-        target.ResourceId = source.ResourceId; target.GrantedUserId = source.GrantedUserId; target.GrantedByUserId = source.GrantedByUserId;
-      }
-    }
-    #endregion
-
-    #region SlaveStatistics
-    public static DT.SlaveStatistics ToDto(DB.SlaveStatistics source) {
-      if (source == null) return null;
-      return new DT.SlaveStatistics {
-        Id = source.StatisticsId,
-        SlaveId = source.SlaveId,
-        Cores = source.Cores,
-        CpuUtilization = source.CpuUtilization,
-        FreeCores = source.FreeCores,
-        FreeMemory = source.FreeMemory,
-        Memory = source.Memory
-      };
-    }
-    public static DB.SlaveStatistics ToEntity(DT.SlaveStatistics source) {
-      if (source == null) return null;
-      var entity = new DB.SlaveStatistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.SlaveStatistics source, DB.SlaveStatistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.SlaveId = source.SlaveId;
-        target.Cores = source.Cores;
-        target.CpuUtilization = source.CpuUtilization;
-        target.FreeCores = source.FreeCores;
-        target.FreeMemory = source.FreeMemory;
-        target.Memory = source.Memory;
-      }
-    }
-    #endregion
-
-    #region Statistics
-    public static DT.Statistics ToDto(DB.Statistics source) {
-      if (source == null) return null;
-      return new DT.Statistics {
-        Id = source.StatisticsId,
-        TimeStamp = source.Timestamp,
-        SlaveStatistics = source.SlaveStatistics.Select(x => Convert.ToDto(x)).ToArray(),
-        UserStatistics = source.UserStatistics.Select(x => Convert.ToDto(x)).ToArray()
-      };
-    }
-    public static DB.Statistics ToEntity(DT.Statistics source) {
-      if (source == null) return null;
-      var entity = new DB.Statistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.Statistics source, DB.Statistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.Timestamp = source.TimeStamp;
-
-      }
-    }
-    #endregion
-
-    #region UserStatistics
-    public static DT.UserStatistics ToDto(DB.UserStatistics source) {
-      if (source == null) return null;
-      return new DT.UserStatistics {
-        Id = source.StatisticsId,
-        UserId = source.UserId,
-        UsedCores = source.UsedCores,
-        ExecutionTime = TimeSpan.FromMilliseconds(source.ExecutionTimeMs),
-        ExecutionTimeFinishedJobs = TimeSpan.FromMilliseconds(source.ExecutionTimeMsFinishedJobs),
-        StartToEndTime = TimeSpan.FromMilliseconds(source.StartToEndTimeMs)
-      };
-    }
-    public static DB.UserStatistics ToEntity(DT.UserStatistics source) {
-      if (source == null) return null;
-      var entity = new DB.UserStatistics(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.UserStatistics source, DB.UserStatistics target) {
-      if ((source != null) && (target != null)) {
-        target.StatisticsId = source.Id;
-        target.UserId = source.UserId;
-        target.UsedCores = source.UsedCores;
-        target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
-        target.ExecutionTimeMsFinishedJobs = source.ExecutionTimeFinishedJobs.TotalMilliseconds;
-        target.StartToEndTimeMs = source.StartToEndTime.TotalMilliseconds;
-      }
-    }
-    #endregion
-
-    #region TaskData
-    public static DT.TaskState ToDto(DB.TaskState source) {
-      if (source == DB.TaskState.Aborted) {
-        return TaskState.Aborted;
-      } else if (source == DB.TaskState.Calculating) {
-        return TaskState.Calculating;
-      } else if (source == DB.TaskState.Failed) {
-        return TaskState.Failed;
-      } else if (source == DB.TaskState.Finished) {
-        return TaskState.Finished;
-      } else if (source == DB.TaskState.Offline) {
-        return TaskState.Offline;
-      } else if (source == DB.TaskState.Paused) {
-        return TaskState.Paused;
-      } else if (source == DB.TaskState.Transferring) {
-        return TaskState.Transferring;
-      } else if (source == DB.TaskState.Waiting) {
-        return TaskState.Waiting;
-      } else
-        return TaskState.Failed;
-    }
-
-    public static DB.TaskState ToEntity(DT.TaskState source) {
-      if (source == DT.TaskState.Aborted) {
-        return DB.TaskState.Aborted;
-      } else if (source == DT.TaskState.Calculating) {
-        return DB.TaskState.Calculating;
-      } else if (source == DT.TaskState.Failed) {
-        return DB.TaskState.Failed;
-      } else if (source == DT.TaskState.Finished) {
-        return DB.TaskState.Finished;
-      } else if (source == DT.TaskState.Offline) {
-        return DB.TaskState.Offline;
-      } else if (source == DT.TaskState.Paused) {
-        return DB.TaskState.Paused;
-      } else if (source == DT.TaskState.Transferring) {
-        return DB.TaskState.Transferring;
-      } else if (source == DT.TaskState.Waiting) {
-        return DB.TaskState.Waiting;
-      } else
-        return DB.TaskState.Failed;
-    }
-    #endregion
-
-    #region Permission
-    public static DT.Permission ToDto(DB.Permission source) {
-      if (source == DB.Permission.Full) {
-        return Permission.Full;
-      } else if (source == DB.Permission.NotAllowed) {
-        return Permission.NotAllowed;
-      } else if (source == DB.Permission.Read) {
-        return Permission.Read;
-      } else
-        return Permission.NotAllowed;
-    }
-
-    public static DB.Permission ToEntity(DT.Permission source) {
-      if (source == DT.Permission.Full) {
-        return DB.Permission.Full;
-      } else if (source == DT.Permission.NotAllowed) {
-        return DB.Permission.NotAllowed;
-      } else if (source == DT.Permission.Read) {
-        return DB.Permission.Read;
-      } else
-        return DB.Permission.NotAllowed;
-    }
-    #endregion
-
-    #region Command
-    public static DT.Command? ToDto(DB.Command? source) {
-      if (source.HasValue) {
-        if (source.Value == DB.Command.Abort) {
-          return Command.Abort;
-        } else if (source.Value == DB.Command.Pause) {
-          return Command.Pause;
-        } else if (source.Value == DB.Command.Stop) {
-          return Command.Stop;
-        } else
-          return Command.Pause;
-      }
-      return null;
-    }
-
-    public static DB.Command? ToEntity(DT.Command? source) {
-      if (source.HasValue) {
-        if (source == DT.Command.Abort) {
-          return DB.Command.Abort;
-        } else if (source == DT.Command.Pause) {
-          return DB.Command.Pause;
-        } else if (source == DT.Command.Stop) {
-          return DB.Command.Stop;
-        } else
-          return DB.Command.Pause;
-      } else
-        return null;
-    }
-    #endregion
-
-    #region CpuArchiteture
-    public static DT.CpuArchitecture ToDto(DB.CpuArchitecture source) {
-      if (source == DB.CpuArchitecture.x64) {
-        return CpuArchitecture.x64;
-      } else if (source == DB.CpuArchitecture.x86) {
-        return CpuArchitecture.x86;
-      } else
-        return CpuArchitecture.x86;
-    }
-
-    public static DB.CpuArchitecture ToEntity(DT.CpuArchitecture source) {
-      if (source == DT.CpuArchitecture.x64) {
-        return DB.CpuArchitecture.x64;
-      } else if (source == DT.CpuArchitecture.x86) {
-        return DB.CpuArchitecture.x86;
-      } else
-        return DB.CpuArchitecture.x86;
-    }
-    #endregion
-
-    #region SlaveState
-    public static DT.SlaveState ToDto(DB.SlaveState source) {
-      if (source == DB.SlaveState.Calculating) {
-        return SlaveState.Calculating;
-      } else if (source == DB.SlaveState.Idle) {
-        return SlaveState.Idle;
-      } else if (source == DB.SlaveState.Offline) {
-        return SlaveState.Offline;
-      } else
-        return SlaveState.Offline;
-    }
-
-    public static DB.SlaveState ToEntity(DT.SlaveState source) {
-      if (source == DT.SlaveState.Calculating) {
-        return DB.SlaveState.Calculating;
-      } else if (source == DT.SlaveState.Idle) {
-        return DB.SlaveState.Idle;
-      } else if (source == DT.SlaveState.Offline) {
-        return DB.SlaveState.Offline;
-      } else
-        return DB.SlaveState.Offline;
-    }
-    #endregion
-
-    #region UserPriority
-    public static DT.UserPriority ToDto(DB.UserPriority source) {
-      if (source == null) return null;
-      return new DT.UserPriority() { Id = source.UserId, DateEnqueued = source.DateEnqueued };
-    }
-    public static DB.UserPriority ToEntity(DT.UserPriority source) {
-      if (source == null) return null;
-      var entity = new DB.UserPriority(); ToEntity(source, entity);
-      return entity;
-    }
-    public static void ToEntity(DT.UserPriority source, DB.UserPriority target) {
-      if ((source != null) && (target != null)) {
-        target.UserId = source.Id;
-        target.DateEnqueued = source.DateEnqueued;
-      }
-    }
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Converter.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Converter.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Converter.cs	(revision 12878)
@@ -0,0 +1,528 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.Hive.DataTransfer;
+
+namespace HeuristicLab.Services.Hive {
+  public static class Converter {
+
+    #region Task
+    public static DT.Task ToDto(this DA.Task source) {
+      if (source == null) return null;
+      return new DT.Task {
+        Id = source.TaskId,
+        State = source.State.ToDto(),
+        ExecutionTime = TimeSpan.FromMilliseconds(source.ExecutionTimeMs),
+        LastHeartbeat = source.LastHeartbeat,
+        ParentTaskId = source.ParentTaskId,
+        Priority = source.Priority,
+        CoresNeeded = source.CoresNeeded,
+        MemoryNeeded = source.MemoryNeeded,
+        IsParentTask = source.IsParentTask,
+        FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished,
+        Command = source.Command.ToDto(),
+        JobId = source.JobId,
+        IsPrivileged = source.IsPrivileged,
+        PluginsNeededIds = source.RequiredPlugins.Select(x => x.PluginId).ToList(),
+        StateLog = source.StateLogs.Select(x => x.ToDto()).OrderBy(x => x.DateTime).ToList(),
+        LastTaskDataUpdate = source.JobData == null ? DateTime.MinValue : source.JobData.LastUpdate
+      };
+    }
+
+    public static DA.Task ToEntity(this DT.Task source) {
+      if (source == null) return null;
+      var result = new DA.Task();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.Task source, DA.Task target) {
+      if ((source == null) || (target == null)) return;
+      target.TaskId = source.Id;
+      target.State = source.State.ToEntity();
+      target.ExecutionTimeMs = source.ExecutionTime.TotalMilliseconds;
+      target.LastHeartbeat = source.LastHeartbeat;
+      target.ParentTaskId = source.ParentTaskId;
+      target.Priority = source.Priority;
+      target.CoresNeeded = source.CoresNeeded;
+      target.MemoryNeeded = source.MemoryNeeded;
+      target.IsParentTask = source.IsParentTask;
+      target.FinishWhenChildJobsFinished = source.FinishWhenChildJobsFinished;
+      target.Command = source.Command.ToEntity();
+      target.JobId = source.JobId;
+      target.IsPrivileged = source.IsPrivileged;
+      var ids = target.RequiredPlugins.Select(x => x.PluginId);
+      target.RequiredPlugins.AddRange(source.PluginsNeededIds
+        .Where(x => !ids.Contains(x))
+        .Select(x => new DA.RequiredPlugin {
+          PluginId = x
+        })
+      );
+      target.StateLogs.AddRange(source.StateLog
+        .Where(x => x.Id == Guid.Empty)
+        .Select(x => x.ToEntity())
+      );
+      // result.JobData missing
+      // result.AssignedResources missing
+    }
+    #endregion
+
+    #region TaskData
+    public static DT.TaskData ToDto(this DA.TaskData source) {
+      if (source == null) return null;
+      return new DT.TaskData {
+        TaskId = source.TaskId,
+        Data = source.Data,
+        LastUpdate = source.LastUpdate
+      };
+    }
+
+    public static DA.TaskData ToEntity(this DT.TaskData source) {
+      if (source == null) return null;
+      var result = new DA.TaskData();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.TaskData source, DA.TaskData target) {
+      if ((source == null) || (target == null)) return;
+      target.TaskId = source.TaskId;
+      target.Data = source.Data;
+      target.LastUpdate = source.LastUpdate;
+    }
+    #endregion
+
+    #region Job
+    public static DT.Job ToDto(this DA.Job source) {
+      return new DT.Job {
+        Id = source.JobId,
+        Description = source.Description,
+        Name = source.Name,
+        OwnerUserId = source.OwnerUserId,
+        DateCreated = source.DateCreated,
+        ResourceNames = source.ResourceIds
+      };
+    }
+
+    public static DA.Job ToEntity(this DT.Job source) {
+      if (source == null) return null;
+      var result = new DA.Job();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.Job source, DA.Job target) {
+      if ((source == null) || (target == null)) return;
+      target.JobId = source.Id;
+      target.Description = source.Description;
+      target.Name = source.Name;
+      target.OwnerUserId = source.OwnerUserId;
+      target.DateCreated = source.DateCreated;
+      target.ResourceIds = source.ResourceNames;
+    }
+    #endregion
+
+    #region JobPermission
+    public static DT.JobPermission ToDto(this DA.JobPermission source) {
+      if (source == null) return null;
+      return new DT.JobPermission {
+        JobId = source.JobId,
+        GrantedUserId = source.GrantedUserId,
+        GrantedByUserId = source.GrantedByUserId,
+        Permission = source.Permission.ToDto()
+      };
+    }
+
+    public static DA.JobPermission ToEntity(this DT.JobPermission source) {
+      if (source == null) return null;
+      var result = new DA.JobPermission();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.JobPermission source, DA.JobPermission target) {
+      if ((source == null) || (target == null)) return;
+      target.JobId = source.JobId;
+      target.GrantedUserId = source.GrantedUserId;
+      target.GrantedByUserId = source.GrantedByUserId;
+      target.Permission = source.Permission.ToEntity();
+    }
+    #endregion
+
+    #region Slave
+    public static DT.Slave ToDto(this DA.Slave source) {
+      if (source == null) return null;
+      return new DT.Slave {
+        Id = source.ResourceId,
+        ParentResourceId = source.ParentResourceId,
+        Cores = source.Cores,
+        CpuSpeed = source.CpuSpeed,
+        FreeCores = source.FreeCores,
+        FreeMemory = source.FreeMemory,
+        IsAllowedToCalculate = source.IsAllowedToCalculate,
+        Memory = source.Memory,
+        Name = source.Name,
+        SlaveState = source.SlaveState.ToDto(),
+        CpuArchitecture = source.CpuArchitecture.ToDto(),
+        OperatingSystem = source.OperatingSystem,
+        LastHeartbeat = source.LastHeartbeat,
+        CpuUtilization = source.CpuUtilization,
+        HbInterval = source.HbInterval,
+        IsDisposable = source.IsDisposable,
+        OwnerUserId = source.OwnerUserId
+      };
+    }
+    public static DA.Slave ToEntity(this DT.Slave source) {
+      if (source == null) return null;
+      var result = new DA.Slave();
+      source.CopyToEntity(result);
+      return result;
+    }
+    public static void CopyToEntity(this DT.Slave source, DA.Slave target) {
+      if ((source == null) || (target == null)) return;
+      target.ResourceId = source.Id;
+      target.ParentResourceId = source.ParentResourceId;
+      target.Cores = source.Cores;
+      target.CpuSpeed = source.CpuSpeed;
+      target.FreeCores = source.FreeCores;
+      target.FreeMemory = source.FreeMemory;
+      target.IsAllowedToCalculate = source.IsAllowedToCalculate;
+      target.Memory = source.Memory;
+      target.Name = source.Name;
+      target.SlaveState = source.SlaveState.ToEntity();
+      target.CpuArchitecture = source.CpuArchitecture.ToEntity();
+      target.OperatingSystem = source.OperatingSystem;
+      target.LastHeartbeat = source.LastHeartbeat;
+      target.CpuUtilization = source.CpuUtilization;
+      target.HbInterval = source.HbInterval;
+      target.IsDisposable = source.IsDisposable;
+      target.OwnerUserId = source.OwnerUserId;
+    }
+    #endregion
+
+    #region State
+    public static DT.TaskState ToDto(this DA.TaskState source) {
+      switch (source) {
+        case DA.TaskState.Aborted: return DT.TaskState.Aborted;
+        case DA.TaskState.Calculating: return DT.TaskState.Calculating;
+        case DA.TaskState.Failed: return DT.TaskState.Failed;
+        case DA.TaskState.Finished: return DT.TaskState.Finished;
+        case DA.TaskState.Offline: return DT.TaskState.Offline;
+        case DA.TaskState.Paused: return DT.TaskState.Paused;
+        case DA.TaskState.Transferring: return DT.TaskState.Transferring;
+        case DA.TaskState.Waiting: return DT.TaskState.Waiting;
+        default: return DT.TaskState.Failed;
+      }
+    }
+
+    public static DA.TaskState ToEntity(this DT.TaskState source) {
+      switch (source) {
+        case DT.TaskState.Aborted: return DA.TaskState.Aborted;
+        case DT.TaskState.Calculating: return DA.TaskState.Calculating;
+        case DT.TaskState.Failed: return DA.TaskState.Failed;
+        case DT.TaskState.Finished: return DA.TaskState.Finished;
+        case DT.TaskState.Offline: return DA.TaskState.Offline;
+        case DT.TaskState.Paused: return DA.TaskState.Paused;
+        case DT.TaskState.Transferring: return DA.TaskState.Transferring;
+        case DT.TaskState.Waiting: return DA.TaskState.Waiting;
+        default: return DA.TaskState.Failed;
+      }
+    }
+    #endregion
+
+    #region StateLogs
+    public static DT.StateLog ToDto(this DA.StateLog source) {
+      return new DT.StateLog {
+        Id = source.StateLogId,
+        State = source.State.ToDto(),
+        DateTime = source.DateTime,
+        TaskId = source.TaskId,
+        UserId = source.UserId,
+        SlaveId = source.SlaveId,
+        Exception = source.Exception
+      };
+    }
+
+    public static DA.StateLog ToEntity(this DT.StateLog source) {
+      return new DA.StateLog {
+        StateLogId = source.Id,
+        State = source.State.ToEntity(),
+        DateTime = source.DateTime,
+        TaskId = source.TaskId,
+        UserId = source.UserId,
+        SlaveId = source.SlaveId,
+        Exception = source.Exception
+      };
+    }
+    #endregion
+
+    #region Plugin
+    public static DT.Plugin ToDto(this DA.Plugin source) {
+      if (source == null) return null;
+      return new DT.Plugin {
+        Id = source.PluginId,
+        Name = source.Name,
+        Version = new Version(source.Version),
+        UserId = source.UserId,
+        DateCreated = source.DateCreated,
+        Hash = source.Hash
+      };
+    }
+    public static DA.Plugin ToEntity(this DT.Plugin source) {
+      if (source == null) return null;
+      var result = new DA.Plugin();
+      source.CopyToEntity(result);
+      return result;
+    }
+    public static void CopyToEntity(this DT.Plugin source, DA.Plugin target) {
+      if ((source == null) || (target == null)) return;
+      target.PluginId = source.Id;
+      target.Name = source.Name;
+      target.Version = source.Version.ToString();
+      target.UserId = source.UserId;
+      target.DateCreated = source.DateCreated;
+      target.Hash = source.Hash;
+    }
+    #endregion
+
+    #region PluginData
+    public static DT.PluginData ToDto(this DA.PluginData source) {
+      if (source == null) return null;
+      return new DT.PluginData {
+        Id = source.PluginDataId,
+        PluginId = source.PluginId,
+        Data = source.Data.ToArray(),
+        FileName = source.FileName
+      };
+    }
+
+    public static DA.PluginData ToEntity(this DT.PluginData source) {
+      if (source == null) return null;
+      var result = new DA.PluginData();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.PluginData source, DA.PluginData target) {
+      if ((source == null) || (target == null)) return;
+      target.PluginDataId = source.Id;
+      target.PluginId = source.PluginId;
+      target.Data = source.Data;
+      target.FileName = source.FileName;
+    }
+    #endregion
+
+    #region ResourcePermission
+    public static DT.ResourcePermission ToDto(this DA.ResourcePermission source) {
+      if (source == null) return null;
+      return new DT.ResourcePermission {
+        ResourceId = source.ResourceId,
+        GrantedUserId = source.GrantedUserId,
+        GrantedByUserId = source.GrantedByUserId
+      };
+    }
+    public static DA.ResourcePermission ToEntity(this DT.ResourcePermission source) {
+      if (source == null) return null;
+      var result = new DA.ResourcePermission();
+      source.CopyToEntity(result);
+      return result;
+    }
+    public static void CopyToEntity(this DT.ResourcePermission source, DA.ResourcePermission target) {
+      if ((source == null) || (target == null)) return;
+      target.ResourceId = source.ResourceId;
+      target.GrantedUserId = source.GrantedUserId;
+      target.GrantedByUserId = source.GrantedByUserId;
+    }
+    #endregion
+
+    #region SlaveGroup
+    public static DT.SlaveGroup ToDto(this DA.SlaveGroup source) {
+      if (source == null) return null;
+      return new DT.SlaveGroup {
+        Id = source.ResourceId,
+        Name = source.Name,
+        ParentResourceId = source.ParentResourceId,
+        HbInterval = source.HbInterval,
+        OwnerUserId = source.OwnerUserId
+      };
+    }
+
+    public static DA.SlaveGroup ToEntity(this DT.SlaveGroup source) {
+      if (source == null) return null;
+      var result = new DA.SlaveGroup();
+      source.CopyToEntity(result);
+      return result;
+    }
+
+    public static void CopyToEntity(this DT.SlaveGroup source, DA.SlaveGroup target) {
+      if ((source == null) || (target == null)) return;
+      target.ResourceId = source.Id;
+      target.Name = source.Name;
+      target.ParentResourceId = source.ParentResourceId;
+      target.HbInterval = source.HbInterval;
+      target.OwnerUserId = source.OwnerUserId;
+    }
+    #endregion
+
+    #region Downtimes
+    public static DT.Downtime ToDto(this DA.Downtime source) {
+      if (source == null) return null;
+      return new DT.Downtime {
+        Id = source.DowntimeId,
+        AllDayEvent = source.AllDayEvent,
+        EndDate = source.EndDate,
+        Recurring = source.Recurring,
+        RecurringId = source.RecurringId,
+        ResourceId = source.ResourceId,
+        StartDate = source.StartDate,
+        DowntimeType = source.DowntimeType
+      };
+    }
+    public static DA.Downtime ToEntity(this DT.Downtime source) {
+      if (source == null) return null;
+      var result = new DA.Downtime();
+      source.CopyToEntity(result);
+      return result;
+    }
+    public static void CopyToEntity(this DT.Downtime source, DA.Downtime target) {
+      if ((source == null) || (target == null)) return;
+      target.DowntimeId = source.Id;
+      target.AllDayEvent = source.AllDayEvent;
+      target.EndDate = source.EndDate;
+      target.Recurring = source.Recurring;
+      target.RecurringId = source.RecurringId;
+      target.ResourceId = source.ResourceId;
+      target.StartDate = source.StartDate;
+      target.DowntimeType = source.DowntimeType;
+    }
+    #endregion
+
+
+    #region Command
+    public static DT.Command? ToDto(this DA.Command? source) {
+      if (source.HasValue) {
+        switch (source) {
+          case DA.Command.Abort: return DT.Command.Abort;
+          case DA.Command.Pause: return DT.Command.Pause;
+          case DA.Command.Stop: return DT.Command.Stop;
+          default: return DT.Command.Pause;
+        }
+      }
+      return null;
+    }
+
+    public static DA.Command? ToEntity(this DT.Command? source) {
+      if (source.HasValue) {
+        switch (source) {
+          case DT.Command.Abort: return DA.Command.Abort;
+          case DT.Command.Pause: return DA.Command.Pause;
+          case DT.Command.Stop: return DA.Command.Stop;
+          default: return DA.Command.Pause;
+        }
+      }
+      return null;
+    }
+    #endregion
+
+    #region Permission
+    public static DT.Permission ToDto(this DA.Permission source) {
+      switch (source) {
+        case DA.Permission.Full: return DT.Permission.Full;
+        case DA.Permission.NotAllowed: return DT.Permission.NotAllowed;
+        case DA.Permission.Read: return DT.Permission.Read;
+        default: return DT.Permission.NotAllowed;
+      }
+    }
+
+    public static DA.Permission ToEntity(this DT.Permission source) {
+      switch (source) {
+        case DT.Permission.Full: return DA.Permission.Full;
+        case DT.Permission.NotAllowed: return DA.Permission.NotAllowed;
+        case DT.Permission.Read: return DA.Permission.Read;
+        default: return DA.Permission.NotAllowed;
+      }
+    }
+    #endregion
+
+    #region CpuArchiteture
+    public static DT.CpuArchitecture ToDto(this DA.CpuArchitecture source) {
+      switch (source) {
+        case DA.CpuArchitecture.x64: return DT.CpuArchitecture.x64;
+        case DA.CpuArchitecture.x86: return DT.CpuArchitecture.x86;
+        default: return DT.CpuArchitecture.x86;
+      }
+    }
+
+    public static DA.CpuArchitecture ToEntity(this DT.CpuArchitecture source) {
+      switch (source) {
+        case DT.CpuArchitecture.x64: return DA.CpuArchitecture.x64;
+        case DT.CpuArchitecture.x86: return DA.CpuArchitecture.x86;
+        default: return DA.CpuArchitecture.x86;
+      }
+    }
+    #endregion
+
+    #region SlaveState
+    public static DT.SlaveState ToDto(this DA.SlaveState source) {
+      switch (source) {
+        case DA.SlaveState.Calculating: return DT.SlaveState.Calculating;
+        case DA.SlaveState.Idle: return DT.SlaveState.Idle;
+        case DA.SlaveState.Offline: return DT.SlaveState.Offline;
+        default: return DT.SlaveState.Offline;
+      }
+    }
+
+    public static DA.SlaveState ToEntity(this DT.SlaveState source) {
+      switch (source) {
+        case DT.SlaveState.Calculating: return DA.SlaveState.Calculating;
+        case DT.SlaveState.Idle: return DA.SlaveState.Idle;
+        case DT.SlaveState.Offline: return DA.SlaveState.Offline;
+        default: return DA.SlaveState.Offline;
+      }
+    }
+    #endregion
+
+    #region UserPriority
+    public static DT.UserPriority ToDto(this DA.UserPriority source) {
+      if (source == null) return null;
+      return new DT.UserPriority() {
+        Id = source.UserId,
+        DateEnqueued = source.DateEnqueued
+      };
+    }
+    public static DA.UserPriority ToEntity(this DT.UserPriority source) {
+      if (source == null) return null;
+      var result = new DA.UserPriority();
+      source.CopyToEntity(result);
+      return result;
+    }
+    public static void CopyToEntity(this DT.UserPriority source, DA.UserPriority target) {
+      if ((source == null) || (target == null)) return;
+      target.UserId = source.Id;
+      target.DateEnqueued = source.DateEnqueued;
+    }
+    #endregion
+
+  }
+}
Index: unk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/SlaveStatistics.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/SlaveStatistics.cs	(revision 12877)
+++ 	(revision )
@@ -1,42 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Runtime.Serialization;
-
-namespace HeuristicLab.Services.Hive.DataTransfer {
-  [DataContract]
-  [Serializable]
-  public class SlaveStatistics : HiveItem {
-    [DataMember]
-    public Guid SlaveId { get; set; }
-    [DataMember]
-    public int Cores { get; set; }
-    [DataMember]
-    public int FreeCores { get; set; }
-    [DataMember]
-    public int Memory { get; set; }
-    [DataMember]
-    public int FreeMemory { get; set; }
-    [DataMember]
-    public double CpuUtilization { get; set; }
-  }
-}
Index: unk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/Statistics.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/Statistics.cs	(revision 12877)
+++ 	(revision )
@@ -1,39 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-
-namespace HeuristicLab.Services.Hive.DataTransfer {
-  [DataContract]
-  [Serializable]
-  public class Statistics : HiveItem {
-    [DataMember]
-    public DateTime TimeStamp { get; set; }
-    [DataMember]
-    public IEnumerable<UserStatistics> UserStatistics { get; set; }
-    [DataMember]
-    public IEnumerable<SlaveStatistics> SlaveStatistics { get; set; }
-
-    public Statistics() { }
-  }
-}
Index: unk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/UserStatistics.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/DataTransfer/UserStatistics.cs	(revision 12877)
+++ 	(revision )
@@ -1,40 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Runtime.Serialization;
-
-namespace HeuristicLab.Services.Hive.DataTransfer {
-  [DataContract]
-  [Serializable]
-  public class UserStatistics : HiveItem {
-    [DataMember]
-    public Guid UserId { get; set; }
-    [DataMember]
-    public int UsedCores { get; set; }
-    [DataMember]
-    public TimeSpan ExecutionTime { get; set; }
-    [DataMember]
-    public TimeSpan ExecutionTimeFinishedJobs { get; set; }
-    [DataMember]
-    public TimeSpan StartToEndTime { get; set; }
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj	(revision 12878)
@@ -118,5 +118,5 @@
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Convert.cs" />
+    <Compile Include="Converter.cs" />
     <Compile Include="DataTransfer\Command.cs" />
     <Compile Include="DataTransfer\UserPriority.cs" />
@@ -139,19 +139,14 @@
     <Compile Include="DataTransfer\SlaveGroup.cs" />
     <Compile Include="DataTransfer\SlaveState.cs" />
-    <Compile Include="DataTransfer\SlaveStatistics.cs" />
     <Compile Include="DataTransfer\StateLog.cs" />
-    <Compile Include="DataTransfer\Statistics.cs" />
-    <Compile Include="DataTransfer\UserStatistics.cs" />
-    <Compile Include="OptimizedHiveDao.cs" />
-    <Compile Include="HiveDao.cs" />
+    <Compile Include="HiveStatisticsGenerator.cs" />
+    <Compile Include="Interfaces\IStatisticsGenerator.cs" />
     <Compile Include="HiveJanitor.cs" />
     <Compile Include="HiveOperationContext.cs" />
-    <Compile Include="Interfaces\IOptimizedHiveDao.cs" />
-    <Compile Include="Interfaces\IHiveDao.cs" />
     <Compile Include="Interfaces\ITaskScheduler.cs" />
+    <Compile Include="PerformanceLogger.cs" />
     <Compile Include="Scheduler\TaskInfoForScheduler.cs" />
     <Compile Include="Scheduler\JobInfoForScheduler.cs" />
     <Compile Include="Scheduler\RoundRobinTaskScheduler.cs" />
-    <Compile Include="Settings.cs" />
     <None Include="app.config" />
     <None Include="Plugin.cs.frame" />
@@ -182,4 +177,8 @@
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Access.DataAccess\3.3\HeuristicLab.Services.Access.DataAccess-3.3.csproj">
+      <Project>{0F652437-998A-4EAB-8BF1-444B5FE8CE97}</Project>
+      <Name>HeuristicLab.Services.Access.DataAccess-3.3</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\HeuristicLab.Services.Access\3.3\HeuristicLab.Services.Access-3.3.csproj">
       <Project>{9FAC0B23-2730-452A-9BA0-D7CA1746C541}</Project>
Index: unk/sources/HeuristicLab.Services.Hive/3.3/HiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,996 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Data.Linq;
-using System.Linq;
-using System.Linq.Expressions;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public class HiveDao : IHiveDao {
-    public static HiveDataContext CreateContext(bool longRunning = false) {
-      var context = new HiveDataContext(Settings.Default.HeuristicLab_Hive_LinqConnectionString);
-      if (longRunning) context.CommandTimeout = (int)Settings.Default.LongRunningDatabaseCommandTimeout.TotalSeconds;
-      return context;
-    }
-
-    #region Task Methods
-    public DT.Task GetTask(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Tasks.SingleOrDefault(x => x.TaskId == id));
-      }
-    }
-
-    public IEnumerable<DT.Task> GetTasks(Expression<Func<Task, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Tasks.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public IEnumerable<DT.LightweightTask> GetLightweightTasks(Expression<Func<Task, bool>> predicate) {
-      List<DT.LightweightTask> tasks = new List<DT.LightweightTask>();
-
-      using (var db = CreateContext()) {
-        var tasksQuery = db.Tasks.Where(predicate).Select(task => new { task.TaskId, task.ExecutionTimeMs, task.ParentTaskId, task.StateLogs, task.State, task.Command });
-        var taskDatasQuery = db.Tasks.Where(predicate).Where(task => task.JobData != null).Select(task => new { task.TaskId, task.JobData.LastUpdate });
-
-        foreach (var task in tasksQuery) {
-          DT.LightweightTask t = new DT.LightweightTask();
-          t.Id = task.TaskId;
-          t.ExecutionTime = TimeSpan.FromMilliseconds(task.ExecutionTimeMs);
-          t.ParentTaskId = task.ParentTaskId;
-          t.StateLog = task.StateLogs == null ? new List<DT.StateLog>() : task.StateLogs.Select(x => DataTransfer.Convert.ToDto(x)).OrderBy(x => x.DateTime).ToList();
-          t.State = DataTransfer.Convert.ToDto(task.State);
-          t.Command = DataTransfer.Convert.ToDto(task.Command);
-          t.LastTaskDataUpdate = taskDatasQuery.Where(x => x.TaskId == task.TaskId).Count() > 0 ? taskDatasQuery.Select(x => x.LastUpdate).First() : DateTime.MinValue;
-          tasks.Add(t);
-        }
-      }
-      return tasks;
-    }
-
-    public IEnumerable<DT.LightweightTask> GetLightweightTasksWithoutStateLog(Expression<Func<Task, bool>> predicate) {
-      List<DT.LightweightTask> tasks = new List<DT.LightweightTask>();
-
-      using (var db = CreateContext()) {
-        var tasksQuery = db.Tasks.Where(predicate).Select(task => new { task.TaskId, task.ExecutionTimeMs, task.ParentTaskId, task.State, task.Command });
-        var taskDatasQuery = db.Tasks.Where(predicate).Where(task => task.JobData != null).Select(task => new { task.TaskId, task.JobData.LastUpdate });
-
-        foreach (var task in tasksQuery) {
-          DT.LightweightTask t = new DT.LightweightTask();
-          t.Id = task.TaskId;
-          t.ExecutionTime = TimeSpan.FromMilliseconds(task.ExecutionTimeMs);
-          t.ParentTaskId = task.ParentTaskId;
-          t.StateLog = new List<DT.StateLog>();
-          t.State = DataTransfer.Convert.ToDto(task.State);
-          t.Command = DataTransfer.Convert.ToDto(task.Command);
-          t.LastTaskDataUpdate = taskDatasQuery.Where(x => x.TaskId == task.TaskId).Count() > 0 ? taskDatasQuery.Select(x => x.LastUpdate).First() : DateTime.MinValue;
-          tasks.Add(t);
-        }
-      }
-      return tasks;
-    }
-
-    public Guid AddTask(DT.Task dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Tasks.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        foreach (Guid pluginId in dto.PluginsNeededIds) {
-          db.RequiredPlugins.InsertOnSubmit(new RequiredPlugin() { TaskId = entity.TaskId, PluginId = pluginId });
-        }
-        db.SubmitChanges();
-        return entity.TaskId;
-      }
-    }
-
-    public void UpdateTaskAndPlugins(DT.Task dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Tasks.FirstOrDefault(x => x.TaskId == dto.Id);
-        if (entity == null) db.Tasks.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        foreach (Guid pluginId in dto.PluginsNeededIds) {
-          if (db.RequiredPlugins.Count(p => p.PluginId == pluginId) == 0) {
-            db.RequiredPlugins.InsertOnSubmit(new RequiredPlugin() { TaskId = entity.TaskId, PluginId = pluginId });
-          }
-        }
-        db.SubmitChanges();
-      }
-    }
-
-    public void UpdateTaskAndStateLogs(DT.Task dto) {
-      using (var db = CreateContext()) {
-        DataLoadOptions dlo = new DataLoadOptions();
-        dlo.LoadWith<Task>(x => x.StateLogs);
-        db.LoadOptions = dlo;
-
-        var entity = db.Tasks.FirstOrDefault(x => x.TaskId == dto.Id);
-        if (entity == null) db.Tasks.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void UpdateTask(DT.Task dto) {
-      using (var db = CreateContext()) {
-        db.DeferredLoadingEnabled = false;
-
-        var entity = db.Tasks.FirstOrDefault(x => x.TaskId == dto.Id);
-        if (entity == null) db.Tasks.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntityTaskOnly(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteTask(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Tasks.FirstOrDefault(x => x.TaskId == id);
-        if (entity != null) db.Tasks.DeleteOnSubmit(entity);
-        db.SubmitChanges(); // taskData and child tasks are deleted by db-trigger
-      }
-    }
-
-    /// <summary>
-    /// returns all parent tasks which are waiting for their child tasks to finish
-    /// </summary>
-    /// <param name="resourceIds">list of resourceids which for which the task should be valid</param>
-    /// <param name="count">maximum number of task to return</param>
-    /// <param name="finished">if true, all parent task which have FinishWhenChildJobsFinished=true are returned, otherwise only FinishWhenChildJobsFinished=false are returned</param>
-    /// <returns></returns>
-    public IEnumerable<DT.Task> GetParentTasks(IEnumerable<Guid> resourceIds, int count, bool finished) {
-      using (var db = CreateContext()) {
-        var query = from ar in db.AssignedResources
-                    where resourceIds.Contains(ar.ResourceId)
-                       && ar.Task.State == TaskState.Waiting
-                       && ar.Task.IsParentTask
-                       && (finished ? ar.Task.FinishWhenChildJobsFinished : !ar.Task.FinishWhenChildJobsFinished)
-                       && (from child in db.Tasks
-                           where child.ParentTaskId == ar.Task.TaskId
-                           select child.State == TaskState.Finished
-                               || child.State == TaskState.Aborted
-                               || child.State == TaskState.Failed).All(x => x)
-                       && (from child in db.Tasks // avoid returning WaitForChildTasks task where no child-task exist (yet)
-                           where child.ParentTaskId == ar.Task.TaskId
-                           select child).Count() > 0
-                    orderby ar.Task.Priority descending, db.Random()
-                    select DT.Convert.ToDto(ar.Task);
-        return count == 0 ? query.ToArray() : query.Take(count).ToArray();
-      }
-    }
-
-    public IEnumerable<TaskInfoForScheduler> GetWaitingTasks(DT.Slave slave) {
-      using (var db = CreateContext()) {
-        var resourceIds = GetParentResources(slave.Id).Select(r => r.Id);
-        //Originally we checked here if there are parent tasks which should be calculated (with GetParentTasks(resourceIds, count, false);).
-        //Because there is at the moment no case where this makes sense (there don't exist parent tasks which need to be calculated), 
-        //we skip this step because it's wasted runtime
-
-        var query = from ar in db.AssignedResources
-                    where resourceIds.Contains(ar.ResourceId)
-                       && !(ar.Task.IsParentTask && ar.Task.FinishWhenChildJobsFinished)
-                       && ar.Task.State == TaskState.Waiting
-                       && ar.Task.CoresNeeded <= slave.FreeCores
-                       && ar.Task.MemoryNeeded <= slave.FreeMemory
-                    select new TaskInfoForScheduler() { TaskId = ar.Task.TaskId, JobId = ar.Task.JobId, Priority = ar.Task.Priority };
-        var waitingTasks = query.ToArray();
-        return waitingTasks;
-      }
-    }
-
-    public DT.Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception) {
-      using (var db = CreateContext()) {
-        db.DeferredLoadingEnabled = false;
-
-        db.StateLogs.InsertOnSubmit(new StateLog {
-          TaskId = taskId,
-          State = taskState,
-          SlaveId = slaveId,
-          UserId = userId,
-          Exception = exception,
-          DateTime = DateTime.Now
-        });
-
-        var task = db.Tasks.SingleOrDefault(x => x.TaskId == taskId);
-        task.State = taskState;
-        db.SubmitChanges();
-      }
-
-      using (var db = CreateContext()) {
-        var task = db.Tasks.SingleOrDefault(x => x.TaskId == taskId);
-        return DT.Convert.ToDto(task);
-      }
-    }
-    #endregion
-
-    #region TaskData Methods
-    public DT.TaskData GetTaskData(Guid id) {
-      using (var db = CreateContext(true)) {
-        return DT.Convert.ToDto(db.TaskDatas.SingleOrDefault(x => x.TaskId == id));
-      }
-    }
-
-    public IEnumerable<DT.TaskData> GetTaskDatas(Expression<Func<TaskData, bool>> predicate) {
-      using (var db = CreateContext(true)) {
-        return db.TaskDatas.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddTaskData(DT.TaskData dto) {
-      using (var db = CreateContext(true)) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.TaskDatas.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.TaskId;
-      }
-    }
-
-    public void UpdateTaskData(DT.TaskData dto) {
-      using (var db = CreateContext(true)) {
-        var entity = db.TaskDatas.FirstOrDefault(x => x.TaskId == dto.TaskId);
-        if (entity == null) db.TaskDatas.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteTaskData(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.TaskDatas.FirstOrDefault(x => x.TaskId == id); // check if all the byte[] is loaded into memory here. otherwise work around to delete without loading it
-        if (entity != null) db.TaskDatas.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region StateLog Methods
-    public DT.StateLog GetStateLog(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.StateLogs.SingleOrDefault(x => x.StateLogId == id));
-      }
-    }
-
-    public IEnumerable<DT.StateLog> GetStateLogs(Expression<Func<StateLog, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.StateLogs.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddStateLog(DT.StateLog dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.StateLogs.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.StateLogId;
-      }
-    }
-
-    public void UpdateStateLog(DT.StateLog dto) {
-      using (var db = CreateContext()) {
-        var entity = db.StateLogs.FirstOrDefault(x => x.StateLogId == dto.Id);
-        if (entity == null) db.StateLogs.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteStateLog(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.StateLogs.FirstOrDefault(x => x.StateLogId == id);
-        if (entity != null) db.StateLogs.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Job Methods
-    public DT.Job GetJob(Guid id) {
-      using (var db = CreateContext()) {
-        return AddStatsToJob(db, DT.Convert.ToDto(db.Jobs.SingleOrDefault(x => x.JobId == id)));
-      }
-    }
-
-    private DT.Job AddStatsToJob(HiveDataContext db, DT.Job exp) {
-      if (exp == null)
-        return null;
-
-      var jobs = db.Tasks.Where(j => j.JobId == exp.Id);
-      exp.JobCount = jobs.Count();
-      exp.CalculatingCount = jobs.Count(j => j.State == TaskState.Calculating);
-      exp.FinishedCount = jobs.Count(j => j.State == TaskState.Finished || j.State == TaskState.Aborted || j.State == TaskState.Failed);
-      return exp;
-    }
-
-    public IEnumerable<DT.Job> GetJobs(Expression<Func<Job, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Jobs.Where(predicate).Select(x => AddStatsToJob(db, DT.Convert.ToDto(x))).ToArray();
-      }
-    }
-
-    public IEnumerable<JobInfoForScheduler> GetJobInfoForScheduler(Expression<Func<Job, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Jobs.Where(predicate).Select(x => new JobInfoForScheduler() { Id = x.JobId, DateCreated = x.DateCreated, OwnerUserId = x.OwnerUserId }).ToArray();
-      }
-    }
-
-    public Guid AddJob(DT.Job dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Jobs.InsertOnSubmit(entity);
-        if (!db.UserPriorities.Any(x => x.UserId == dto.OwnerUserId))
-          EnqueueUserPriority(new DT.UserPriority { Id = dto.OwnerUserId, DateEnqueued = dto.DateCreated });
-        db.SubmitChanges();
-        return entity.JobId;
-      }
-    }
-
-    public void UpdateJob(DT.Job dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Jobs.FirstOrDefault(x => x.JobId == dto.Id);
-        if (entity == null) db.Jobs.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteJob(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Jobs.FirstOrDefault(x => x.JobId == id);
-        if (entity != null) db.Jobs.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region JobPermission Methods
-    public DT.JobPermission GetJobPermission(Guid jobId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.JobPermissions.SingleOrDefault(x => x.JobId == jobId && x.GrantedUserId == grantedUserId));
-      }
-    }
-
-    public IEnumerable<DT.JobPermission> GetJobPermissions(Expression<Func<JobPermission, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.JobPermissions.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public void AddJobPermission(DT.JobPermission dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.JobPermissions.InsertOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void UpdateJobPermission(DT.JobPermission dto) {
-      using (var db = CreateContext()) {
-        var entity = db.JobPermissions.FirstOrDefault(x => x.JobId == dto.JobId && x.GrantedUserId == dto.GrantedUserId);
-        if (entity == null) db.JobPermissions.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteJobPermission(Guid jobId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        var entity = db.JobPermissions.FirstOrDefault(x => x.JobId == jobId && x.GrantedUserId == grantedUserId);
-        if (entity != null) db.JobPermissions.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    /// <summary>
-    /// Sets the permissions for a experiment. makes sure that only one permission per user exists.
-    /// </summary>
-    public void SetJobPermission(Guid jobId, Guid grantedByUserId, Guid grantedUserId, Permission permission) {
-      using (var db = CreateContext()) {
-        JobPermission jobPermission = db.JobPermissions.SingleOrDefault(x => x.JobId == jobId && x.GrantedUserId == grantedUserId);
-        if (jobPermission != null) {
-          if (permission == Permission.NotAllowed) {
-            // not allowed, delete
-            db.JobPermissions.DeleteOnSubmit(jobPermission);
-          } else {
-            // update
-            jobPermission.Permission = permission;
-            jobPermission.GrantedByUserId = grantedByUserId; // update grantedByUserId, always the last "granter" is stored
-          }
-        } else {
-          // insert
-          if (permission != Permission.NotAllowed) {
-            jobPermission = new JobPermission() { JobId = jobId, GrantedByUserId = grantedByUserId, GrantedUserId = grantedUserId, Permission = permission };
-            db.JobPermissions.InsertOnSubmit(jobPermission);
-          }
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Plugin Methods
-    public DT.Plugin GetPlugin(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Plugins.SingleOrDefault(x => x.PluginId == id));
-      }
-    }
-
-    public IEnumerable<DT.Plugin> GetPlugins(Expression<Func<Plugin, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Plugins.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddPlugin(DT.Plugin dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Plugins.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.PluginId;
-      }
-    }
-
-    public void UpdatePlugin(DT.Plugin dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Plugins.FirstOrDefault(x => x.PluginId == dto.Id);
-        if (entity == null) db.Plugins.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeletePlugin(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Plugins.FirstOrDefault(x => x.PluginId == id);
-        if (entity != null) db.Plugins.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region PluginData Methods
-    public DT.PluginData GetPluginData(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.PluginDatas.SingleOrDefault(x => x.PluginDataId == id));
-      }
-    }
-
-    public IEnumerable<DT.PluginData> GetPluginDatas(Expression<Func<PluginData, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.PluginDatas.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddPluginData(DT.PluginData dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.PluginDatas.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.PluginDataId;
-      }
-    }
-
-    public void UpdatePluginData(DT.PluginData dto) {
-      using (var db = CreateContext()) {
-        var entity = db.PluginDatas.FirstOrDefault(x => x.PluginId == dto.PluginId);
-        if (entity == null) db.PluginDatas.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeletePluginData(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.PluginDatas.FirstOrDefault(x => x.PluginDataId == id);
-        if (entity != null) db.PluginDatas.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Slave Methods
-    public DT.Slave GetSlave(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Resources.OfType<Slave>().SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.Slave> GetSlaves(Expression<Func<Slave, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.OfType<Slave>().Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddSlave(DT.Slave dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateSlave(DT.Slave dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<Slave>().FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteSlave(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<Slave>().FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) db.Resources.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region SlaveGroup Methods
-    public DT.SlaveGroup GetSlaveGroup(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Resources.OfType<SlaveGroup>().SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.SlaveGroup> GetSlaveGroups(Expression<Func<SlaveGroup, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.OfType<SlaveGroup>().Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddSlaveGroup(DT.SlaveGroup dto) {
-      using (var db = CreateContext()) {
-        if (dto.Id == Guid.Empty)
-          dto.Id = Guid.NewGuid();
-        var entity = DT.Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateSlaveGroup(DT.SlaveGroup dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<SlaveGroup>().FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteSlaveGroup(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.OfType<SlaveGroup>().FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) {
-          if (db.Resources.Where(r => r.ParentResourceId == id).Count() > 0) {
-            throw new InvalidOperationException("Cannot delete SlaveGroup as long as there are Slaves in the group");
-          }
-          db.Resources.DeleteOnSubmit(entity);
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Resource Methods
-    public DT.Resource GetResource(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Resources.SingleOrDefault(x => x.ResourceId == id));
-      }
-    }
-
-    public IEnumerable<DT.Resource> GetResources(Expression<Func<Resource, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Resources.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddResource(DT.Resource dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Resources.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.ResourceId;
-      }
-    }
-
-    public void UpdateResource(DT.Resource dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.FirstOrDefault(x => x.ResourceId == dto.Id);
-        if (entity == null) db.Resources.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteResource(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Resources.FirstOrDefault(x => x.ResourceId == id);
-        if (entity != null) db.Resources.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void AssignJobToResource(Guid taskId, IEnumerable<Guid> resourceIds) {
-      using (var db = CreateContext()) {
-        db.DeferredLoadingEnabled = false;
-
-        List<AssignedResource> assignedResources = new List<AssignedResource>();
-        foreach (Guid rId in resourceIds) {
-          assignedResources.Add(new AssignedResource() { TaskId = taskId, ResourceId = rId });
-        }
-        db.AssignedResources.InsertAllOnSubmit(assignedResources);
-        db.SubmitChanges();
-      }
-    }
-
-    public IEnumerable<DT.Resource> GetAssignedResources(Guid jobId) {
-      using (var db = CreateContext()) {
-        var job = db.Tasks.Where(x => x.TaskId == jobId).Single();
-        return job.AssignedResources.Select(x => DT.Convert.ToDto(x.Resource)).ToArray();
-      }
-    }
-
-    /// <summary>
-    /// Returns all parent resources of a resource (the given resource is also added)
-    /// </summary>
-    public IEnumerable<DT.Resource> GetParentResources(Guid resourceId) {
-      using (var db = CreateContext()) {
-        var resources = new List<Resource>();
-        CollectParentResources(resources, db.Resources.Where(r => r.ResourceId == resourceId).Single());
-        return resources.Select(r => DT.Convert.ToDto(r)).ToArray();
-      }
-    }
-
-    private static void CollectParentResources(ICollection<Resource> resources, Resource resource) {
-      if (resource == null) return;
-      resources.Add(resource);
-      CollectParentResources(resources, resource.ParentResource);
-    }
-
-    /// <summary>
-    /// Returns all child resources of a resource (without the given resource)
-    /// </summary>
-    public IEnumerable<DT.Resource> GetChildResources(Guid resourceId) {
-      using (var db = CreateContext()) {
-        return CollectChildResources(resourceId, db);
-      }
-    }
-
-    public IEnumerable<DT.Resource> CollectChildResources(Guid resourceId, HiveDataContext db) {
-      var childs = new List<DT.Resource>();
-      foreach (var child in db.Resources.Where(x => x.ParentResourceId == resourceId)) {
-        childs.Add(DT.Convert.ToDto(child));
-        childs.AddRange(CollectChildResources(child.ResourceId, db));
-      }
-      return childs;
-    }
-
-    public IEnumerable<DT.Task> GetJobsByResourceId(Guid resourceId) {
-      using (var db = CreateContext()) {
-        var resources = GetChildResources(resourceId).Select(x => x.Id).ToList();
-        resources.Add(resourceId);
-
-        var jobs = db.Tasks.Where(j =>
-          j.State == TaskState.Calculating &&
-          j.StateLogs.OrderByDescending(x => x.DateTime).First().SlaveId.HasValue &&
-          resources.Contains(j.StateLogs.OrderByDescending(x => x.DateTime).First().SlaveId.Value));
-        return jobs.Select(j => DT.Convert.ToDto(j)).ToArray();
-      }
-    }
-    #endregion
-
-    #region ResourcePermission Methods
-    public DT.ResourcePermission GetResourcePermission(Guid resourceId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.ResourcePermissions.SingleOrDefault(x => x.ResourceId == resourceId && x.GrantedUserId == grantedUserId));
-      }
-    }
-
-    public IEnumerable<DT.ResourcePermission> GetResourcePermissions(Expression<Func<ResourcePermission, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.ResourcePermissions.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public void AddResourcePermission(DT.ResourcePermission dto) {
-      using (var db = CreateContext()) {
-        var entity = db.ResourcePermissions.SingleOrDefault(x => x.ResourceId == dto.ResourceId && x.GrantedUserId == dto.GrantedUserId);
-        if (entity == null) { db.ResourcePermissions.InsertOnSubmit(DT.Convert.ToEntity(dto)); db.SubmitChanges(); }
-      }
-    }
-
-    public void UpdateResourcePermission(DT.ResourcePermission dto) {
-      using (var db = CreateContext()) {
-        var entity = db.ResourcePermissions.FirstOrDefault(x => x.ResourceId == dto.ResourceId && x.GrantedUserId == dto.GrantedUserId);
-        if (entity == null) db.ResourcePermissions.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteResourcePermission(Guid resourceId, Guid grantedUserId) {
-      using (var db = CreateContext()) {
-        var entity = db.ResourcePermissions.FirstOrDefault(x => x.ResourceId == resourceId && x.GrantedUserId == grantedUserId);
-        if (entity != null) db.ResourcePermissions.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Authorization Methods
-    public Permission GetPermissionForTask(Guid taskId, Guid userId) {
-      using (var db = CreateContext()) {
-        return GetPermissionForJob(GetJobForTask(taskId), userId);
-      }
-    }
-
-    public Permission GetPermissionForJob(Guid jobId, Guid userId) {
-      using (var db = CreateContext()) {
-        Job job = db.Jobs.SingleOrDefault(x => x.JobId == jobId);
-        if (job == null) return Permission.NotAllowed;
-        if (job.OwnerUserId == userId) return Permission.Full;
-        JobPermission permission = db.JobPermissions.SingleOrDefault(p => p.JobId == jobId && p.GrantedUserId == userId);
-        return permission != null ? permission.Permission : Permission.NotAllowed;
-      }
-    }
-
-    public Guid GetJobForTask(Guid taskId) {
-      using (var db = CreateContext()) {
-        return db.Tasks.Single(j => j.TaskId == taskId).JobId;
-      }
-    }
-    #endregion
-
-    #region Lifecycle Methods
-    public DateTime GetLastCleanup() {
-      using (var db = CreateContext()) {
-        var entity = db.Lifecycles.SingleOrDefault();
-        return entity != null ? entity.LastCleanup : DateTime.MinValue;
-      }
-    }
-
-    public void SetLastCleanup(DateTime datetime) {
-      using (var db = CreateContext()) {
-        var entity = db.Lifecycles.SingleOrDefault();
-        if (entity != null) {
-          entity.LastCleanup = datetime;
-        } else {
-          entity = new Lifecycle();
-          entity.LifecycleId = 0; // always only one entry with ID:0
-          entity.LastCleanup = datetime;
-          db.Lifecycles.InsertOnSubmit(entity);
-        }
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Downtime Methods
-    public DT.Downtime GetDowntime(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Downtimes.SingleOrDefault(x => x.DowntimeId == id));
-      }
-    }
-
-    public IEnumerable<DT.Downtime> GetDowntimes(Expression<Func<Downtime, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Downtimes.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddDowntime(DT.Downtime dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Downtimes.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        return entity.DowntimeId;
-      }
-    }
-
-    public void UpdateDowntime(DT.Downtime dto) {
-      using (var db = CreateContext()) {
-        var entity = db.Downtimes.FirstOrDefault(x => x.DowntimeId == dto.Id);
-        if (entity == null) db.Downtimes.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public void DeleteDowntime(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Downtimes.FirstOrDefault(x => x.DowntimeId == id);
-        if (entity != null) db.Downtimes.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Statistics Methods
-    public DT.Statistics GetStatistic(Guid id) {
-      using (var db = CreateContext()) {
-        return DT.Convert.ToDto(db.Statistics.SingleOrDefault(x => x.StatisticsId == id));
-      }
-    }
-
-    public IEnumerable<DT.Statistics> GetStatistics(Expression<Func<Statistics, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.Statistics.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public Guid AddStatistics(DT.Statistics dto) {
-      using (var db = CreateContext()) {
-        var entity = DT.Convert.ToEntity(dto);
-        db.Statistics.InsertOnSubmit(entity);
-        db.SubmitChanges();
-        foreach (var slaveStat in dto.SlaveStatistics) {
-          slaveStat.Id = entity.StatisticsId;
-          db.SlaveStatistics.InsertOnSubmit(DT.Convert.ToEntity(slaveStat));
-        }
-        if (dto.UserStatistics != null) {
-          foreach (var userStat in dto.UserStatistics) {
-            userStat.Id = entity.StatisticsId;
-            db.UserStatistics.InsertOnSubmit(DT.Convert.ToEntity(userStat));
-          }
-        }
-        db.SubmitChanges();
-        return entity.StatisticsId;
-      }
-    }
-
-    public void DeleteStatistics(Guid id) {
-      using (var db = CreateContext()) {
-        var entity = db.Statistics.FirstOrDefault(x => x.StatisticsId == id);
-        if (entity != null) db.Statistics.DeleteOnSubmit(entity);
-        db.SubmitChanges();
-      }
-    }
-
-    public Dictionary<Guid, int> GetWaitingTasksByUserForResources(List<Guid> resourceIds) {
-      using (var db = CreateContext()) {
-        var waitingTasksByUser = from task in db.Tasks
-                                 where task.State == TaskState.Waiting && task.AssignedResources.Any(x => resourceIds.Contains(x.ResourceId))
-                                 group task by task.Job.OwnerUserId into g
-                                 select new { UserId = g.Key, UsedCores = g.Count() };
-        return waitingTasksByUser.ToDictionary(x => x.UserId, x => x.UsedCores);
-      }
-    }
-
-    public Dictionary<Guid, int> GetCalculatingTasksByUserForResources(List<Guid> resourceIds) {
-      using (var db = CreateContext()) {
-        var calculatingTasksByUser = from task in db.Tasks
-                                     where task.State == TaskState.Calculating && task.AssignedResources.Any(x => resourceIds.Contains(x.ResourceId))
-                                     group task by task.Job.OwnerUserId into g
-                                     select new { UserId = g.Key, UsedCores = g.Count() };
-        return calculatingTasksByUser.ToDictionary(x => x.UserId, x => x.UsedCores);
-      }
-    }
-
-    public List<DT.UserStatistics> GetUserStatistics() {
-      using (var db = CreateContext()) {
-        var userStats = new Dictionary<Guid, DT.UserStatistics>();
-
-        var usedCoresByUser = from job in db.Tasks
-                              where job.State == TaskState.Calculating
-                              group job by job.Job.OwnerUserId into g
-                              select new { UserId = g.Key, UsedCores = g.Count() };
-
-        foreach (var item in usedCoresByUser) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].UsedCores += item.UsedCores;
-        }
-
-        var executionTimesByUser = from task in db.Tasks
-                                   group task by task.Job.OwnerUserId into g
-                                   select new { UserId = g.Key, ExecutionTime = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMs).Sum()) };
-        foreach (var item in executionTimesByUser) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTime += item.ExecutionTime;
-        }
-
-        // execution times only of finished task - necessary to compute efficieny
-        var executionTimesFinishedJobs = from job in db.Tasks
-                                         where job.State == TaskState.Finished
-                                         group job by job.Job.OwnerUserId into g
-                                         select new { UserId = g.Key, ExecutionTimeFinishedJobs = TimeSpan.FromMilliseconds(g.Select(x => x.ExecutionTimeMs).Sum()) };
-
-        foreach (var item in executionTimesFinishedJobs) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTimeFinishedJobs += item.ExecutionTimeFinishedJobs;
-        }
-
-        // start to end times only of finished task - necessary to compute efficiency
-        var startToEndTimesFinishedJobs = from job in db.Tasks
-                                          where job.State == TaskState.Finished
-                                          group job by job.Job.OwnerUserId into g
-                                          select new {
-                                            UserId = g.Key,
-                                            StartToEndTime = new TimeSpan(g.Select(x => x.StateLogs.OrderByDescending(sl => sl.DateTime).First().DateTime - x.StateLogs.OrderBy(sl => sl.DateTime).First().DateTime).Sum(ts => ts.Ticks))
-                                          };
-        foreach (var item in startToEndTimesFinishedJobs) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].StartToEndTime += item.StartToEndTime;
-        }
-
-        // also consider executiontimes of DeletedJobStats 
-        var deletedJobsExecutionTimesByUsers = from del in db.DeletedJobStatistics
-                                               group del by del.UserId into g
-                                               select new {
-                                                 UserId = g.Key,
-                                                 ExecutionTime = TimeSpan.FromSeconds(g.Select(x => x.ExecutionTimeS).Sum()),
-                                                 ExecutionTimeFinishedJobs = TimeSpan.FromSeconds(g.Select(x => x.ExecutionTimeSFinishedJobs).Sum()),
-                                                 StartToEndTime = TimeSpan.FromSeconds(g.Select(x => x.StartToEndTimeS).Sum())
-                                               };
-        foreach (var item in deletedJobsExecutionTimesByUsers) {
-          if (!userStats.ContainsKey(item.UserId)) {
-            userStats.Add(item.UserId, new DT.UserStatistics() { UserId = item.UserId });
-          }
-          userStats[item.UserId].ExecutionTime += item.ExecutionTime;
-          userStats[item.UserId].ExecutionTimeFinishedJobs += item.ExecutionTimeFinishedJobs;
-          userStats[item.UserId].StartToEndTime += item.StartToEndTime;
-        }
-
-        return userStats.Values.ToList();
-      }
-    }
-    #endregion
-
-    #region UserPriority Methods
-    public IEnumerable<DT.UserPriority> GetUserPriorities(Expression<Func<UserPriority, bool>> predicate) {
-      using (var db = CreateContext()) {
-        return db.UserPriorities.Where(predicate).Select(x => DT.Convert.ToDto(x)).ToArray();
-      }
-    }
-
-    public void EnqueueUserPriority(DT.UserPriority dto) {
-      using (var db = CreateContext()) {
-        var entity = db.UserPriorities.FirstOrDefault(x => x.UserId == dto.Id);
-        if (entity == null) db.UserPriorities.InsertOnSubmit(DT.Convert.ToEntity(dto));
-        else DT.Convert.ToEntity(dto, entity);
-        db.SubmitChanges();
-      }
-    }
-    #endregion
-
-    #region Helpers
-    private void CollectChildTasks(HiveDataContext db, Guid parentTaskId, List<Task> collection) {
-      var tasks = db.Tasks.Where(j => j.ParentTaskId == parentTaskId);
-      foreach (var task in tasks) {
-        collection.Add(task);
-        if (task.IsParentTask)
-          CollectChildTasks(db, task.TaskId, collection);
-      }
-    }
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs	(revision 12878)
@@ -23,56 +23,81 @@
 using System.Threading;
 using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
 
 namespace HeuristicLab.Services.Hive {
   public class HiveJanitor {
     private bool stop;
-    private AutoResetEvent waitHandle;
+    private AutoResetEvent cleanupWaitHandle;
+    private AutoResetEvent generateStatisticsWaitHandle;
 
-    private DataAccess.ITransactionManager trans {
-      get { return ServiceLocator.Instance.TransactionManager; }
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
     }
-
-    private IEventManager eventManager {
+    private IEventManager EventManager {
       get { return ServiceLocator.Instance.EventManager; }
     }
 
-    private IHiveDao dao {
-      get { return ServiceLocator.Instance.HiveDao; }
+    private IStatisticsGenerator StatisticsGenerator {
+      get { return ServiceLocator.Instance.StatisticsGenerator; }
     }
 
     public HiveJanitor() {
       stop = false;
-      waitHandle = new AutoResetEvent(true);
+      cleanupWaitHandle = new AutoResetEvent(false);
+      generateStatisticsWaitHandle = new AutoResetEvent(false);
     }
 
     public void StopJanitor() {
       stop = true;
-      waitHandle.Set();
+      cleanupWaitHandle.Set();
+      generateStatisticsWaitHandle.Set();
     }
 
-    public void Run() {
+    public void RunCleanup() {
+      var pm = PersistenceManager;
       while (!stop) {
         try {
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting cleanup");
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting cleanup.");
           bool cleanup = false;
-          trans.UseTransaction(() => {
-            DateTime lastCleanup = dao.GetLastCleanup();
-            if (DateTime.Now - lastCleanup > HeuristicLab.Services.Hive.Properties.Settings.Default.CleanupInterval) {
-              dao.SetLastCleanup(DateTime.Now);
+
+          var lifecycleDao = pm.LifecycleDao;
+          pm.UseTransaction(() => {
+            var lifecycle = lifecycleDao.GetLastLifecycle();
+            if (lifecycle == null
+                || DateTime.Now - lifecycle.LastCleanup > Properties.Settings.Default.CleanupInterval) {
+              lifecycleDao.UpdateLifecycle();
               cleanup = true;
             }
+            pm.SubmitChanges();
           }, true);
 
           if (cleanup) {
-            eventManager.Cleanup();
+            EventManager.Cleanup();
           }
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: cleanup finished");
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: cleanup finished.");
         }
         catch (Exception e) {
           LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e.ToString()));
         }
-        waitHandle.WaitOne(HeuristicLab.Services.Hive.Properties.Settings.Default.CleanupInterval);
+        cleanupWaitHandle.WaitOne(Properties.Settings.Default.CleanupInterval);
       }
-      waitHandle.Close();
+      cleanupWaitHandle.Close();
+    }
+
+    public void RunGenerateStatistics() {
+      while (!stop) {
+        try {
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting generate statistics.");
+          StatisticsGenerator.GenerateStatistics();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: generate statistics finished.");
+        }
+        catch (Exception e) {
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e));
+        }
+
+        generateStatisticsWaitHandle.WaitOne(Properties.Settings.Default.GenerateStatisticsInterval);
+      }
+
+      generateStatisticsWaitHandle.Close();
     }
   }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveService.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveService.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveService.cs	(revision 12878)
@@ -23,13 +23,15 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Security;
 using System.ServiceModel;
+using HeuristicLab.Services.Access;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
 using HeuristicLab.Services.Hive.DataTransfer;
+using HeuristicLab.Services.Hive.Manager;
 using HeuristicLab.Services.Hive.ServiceContracts;
 using DA = HeuristicLab.Services.Hive.DataAccess;
 using DT = HeuristicLab.Services.Hive.DataTransfer;
 
-
 namespace HeuristicLab.Services.Hive {
-
   /// <summary>
   /// Implementation of the Hive service (interface <see cref="IHiveService"/>).
@@ -39,197 +41,192 @@
   [HiveOperationContextBehavior]
   public class HiveService : IHiveService {
-    private IHiveDao dao {
-      get { return ServiceLocator.Instance.HiveDao; }
-    }
-    private IOptimizedHiveDao optimizedDao {
-      get { return ServiceLocator.Instance.OptimizedHiveDao; }
-    }
-    private Access.IRoleVerifier authen {
+    private static readonly DA.TaskState[] CompletedStates = { DA.TaskState.Finished, DA.TaskState.Aborted, DA.TaskState.Failed };
+
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
       get { return ServiceLocator.Instance.RoleVerifier; }
     }
-    private IAuthorizationManager author {
+
+    private IAuthorizationManager AuthorizationManager {
       get { return ServiceLocator.Instance.AuthorizationManager; }
     }
-    private DataAccess.ITransactionManager trans {
-      get { return ServiceLocator.Instance.TransactionManager; }
-    }
-    private IEventManager eventManager {
+    private IEventManager EventManager {
       get { return ServiceLocator.Instance.EventManager; }
     }
-    private Access.IUserManager userManager {
-      get { return ServiceLocator.Instance.UserManager; }
-    }
-    private HeartbeatManager heartbeatManager {
+    private HeartbeatManager HeartbeatManager {
       get { return ServiceLocator.Instance.HeartbeatManager; }
     }
 
     #region Task Methods
-    public Guid AddTask(Task task, TaskData taskData, IEnumerable<Guid> resourceIds) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        var t = DT.Convert.ToEntity(task);
-        t.RequiredPlugins.AddRange(task.PluginsNeededIds.Select(pluginId => new DA.RequiredPlugin { Task = t, PluginId = pluginId }));
-
-        t.JobData = DT.Convert.ToEntity(taskData);
-        t.JobData.LastUpdate = DateTime.Now;
-
-        optimizedDao.AddTask(t);
-
-        dao.AssignJobToResource(t.TaskId, resourceIds);
-
-        optimizedDao.UpdateTaskState(t.TaskId, DA.TaskState.Waiting, null, userManager.CurrentUserId, null);
-
-        return t.TaskId;
-      }, false, true);
-    }
-
-    public Guid AddChildTask(Guid parentTaskId, Task task, TaskData taskData) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+    public Guid AddTask(DT.Task task, DT.TaskData taskData, IEnumerable<Guid> resourceIds) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddTask")) {
+        var taskDao = pm.TaskDao;
+        var stateLogDao = pm.StateLogDao;
+        var newTask = task.ToEntity();
+        newTask.JobData = taskData.ToEntity();
+        newTask.JobData.LastUpdate = DateTime.Now;
+        newTask.AssignedResources.AddRange(resourceIds.Select(
+          x => new DA.AssignedResource {
+            ResourceId = x
+          }));
+        newTask.State = DA.TaskState.Waiting;
+        return pm.UseTransaction(() => {
+          taskDao.Save(newTask);
+          pm.SubmitChanges();
+          stateLogDao.Save(new DA.StateLog {
+            State = DA.TaskState.Waiting,
+            DateTime = DateTime.Now,
+            TaskId = newTask.TaskId,
+            UserId = UserManager.CurrentUserId,
+            SlaveId = null,
+            Exception = null
+          });
+          pm.SubmitChanges();
+          return newTask.TaskId;
+        }, false, true);
+      }
+    }
+
+    public Guid AddChildTask(Guid parentTaskId, DT.Task task, DT.TaskData taskData) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      IEnumerable<Guid> resourceIds;
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddChildTask")) {
+        var assignedResourceDao = pm.AssignedResourceDao;
+        resourceIds = pm.UseTransaction(() => {
+          return assignedResourceDao.GetByTaskId(parentTaskId)
+            .Select(x => x.ResourceId)
+            .ToList();
+        });
+      }
       task.ParentTaskId = parentTaskId;
-      return AddTask(task, taskData, optimizedDao.GetAssignedResourceIds(parentTaskId).ToList());
-    }
-
-    public Task GetTask(Guid taskId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      author.AuthorizeForTask(taskId, Permission.Read);
-
-      return trans.UseTransaction(() => {
-        return DT.Convert.ToDto(optimizedDao.GetTaskById(taskId));
-      }, false, false);
-    }
-
-    public IEnumerable<Task> GetTasks() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        var tasks = dao.GetTasks(x => true);
-        foreach (var task in tasks)
-          author.AuthorizeForTask(task.Id, Permission.Read);
-        return tasks;
-      });
-    }
-
-    public IEnumerable<LightweightTask> GetLightweightTasks(IEnumerable<Guid> 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<LightweightTask> 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<LightweightTask> GetLightweightJobTasks(Guid jobId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForJob(jobId, Permission.Read);
-
-      return trans.UseTransaction(() => {
-        return optimizedDao.GetLightweightTasks(jobId).ToArray();
-      }, false, true);
-    }
-
-    public IEnumerable<LightweightTask> GetLightweightJobTasksWithoutStateLog(Guid jobId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForJob(jobId, Permission.Read);
-
-      return trans.UseTransaction(() => {
-        return dao.GetLightweightTasksWithoutStateLog(task => task.JobId == jobId).ToArray();
-      }, false, false);
-    }
-
-    public TaskData GetTaskData(Guid taskId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      author.AuthorizeForTask(taskId, Permission.Read);
-
-      return trans.UseTransaction(() => {
-        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(() => {
-        var task = optimizedDao.GetTaskByDto(taskDto);
-        optimizedDao.UpdateTask(task);
-      });
-    }
-
-    public void UpdateTaskData(Task task, TaskData taskData) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      author.AuthorizeForTask(task.Id, Permission.Full);
-
-      trans.UseTransaction(() => {
-        var t = optimizedDao.GetTaskByDto(task);
-        optimizedDao.UpdateTask(t);
-      });
-
-      trans.UseTransaction(() => {
-        var data = optimizedDao.GetTaskDataByDto(taskData);
-        data.LastUpdate = DateTime.Now;
-        optimizedDao.UpdateTaskData(data);
-      });
-    }
-
-    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(() => {
-        var task = optimizedDao.UpdateTaskState(taskId, DT.Convert.ToEntity(taskState), slaveId, userId, exception);
-
-        if (task.Command.HasValue && task.Command.Value == DA.Command.Pause && task.State == DA.TaskState.Paused) {
-          task.Command = null;
-        } else if (task.Command.HasValue && task.Command.Value == DA.Command.Abort && task.State == DA.TaskState.Aborted) {
-          task.Command = null;
-        } else if (task.Command.HasValue && task.Command.Value == DA.Command.Stop && task.State == DA.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 = optimizedDao.UpdateTaskState(taskId, DA.TaskState.Waiting, slaveId, userId, exception);
-        }
-
-        return DT.Convert.ToDto(task);
-      });
-    }
-
-    public IEnumerable<Task> 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;
+      return AddTask(task, taskData, resourceIds);
+    }
+
+    public DT.Task GetTask(Guid taskId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Read);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetTask")) {
+        var taskDao = pm.TaskDao;
+        return pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskId);
+          return task.ToDto();
+        });
+      }
+    }
+
+    public IEnumerable<DT.LightweightTask> GetLightweightJobTasks(Guid jobId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, Permission.Read);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetLightweightJobTasks")) {
+        var taskDao = pm.TaskDao;
+        return pm.UseTransaction(() => {
+          return taskDao.GetByJobId(jobId)
+            .ToList()
+            .Select(x => new DT.LightweightTask {
+              Id = x.TaskId,
+              ExecutionTime = TimeSpan.FromMilliseconds(x.ExecutionTimeMs),
+              ParentTaskId = x.ParentTaskId,
+              StateLog = x.StateLogs.OrderBy(y => y.DateTime)
+                                    .Select(z => z.ToDto())
+                                    .ToList(),
+              State = x.State.ToDto(),
+              Command = x.Command.ToDto(),
+              LastTaskDataUpdate = x.JobData.LastUpdate
+            })
+            .ToList();
+        }, false, true);
+      }
+    }
+
+    public IEnumerable<DT.LightweightTask> GetLightweightJobTasksWithoutStateLog(Guid jobId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, Permission.Read);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetLightweightJobTasksWithoutStateLog")) {
+        var taskDao = pm.TaskDao;
+        return pm.UseTransaction(() => {
+          return taskDao.GetByJobId(jobId)
+            .ToList()
+            .Select(x => new DT.LightweightTask {
+              Id = x.TaskId,
+              ExecutionTime = TimeSpan.FromMilliseconds(x.ExecutionTimeMs),
+              ParentTaskId = x.ParentTaskId,
+              StateLog = new List<DT.StateLog>(),
+              State = x.State.ToDto(),
+              Command = x.Command.ToDto(),
+              LastTaskDataUpdate = x.JobData.LastUpdate
+            })
+            .ToList();
+        }, false, true);
+      }
+    }
+
+    public DT.TaskData GetTaskData(Guid taskId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Read);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetTaskData")) {
+        var taskDataDao = pm.TaskDataDao;
+        return pm.UseTransaction(() => taskDataDao.GetById(taskId).ToDto());
+      }
+    }
+
+    public void UpdateTask(DT.Task taskDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskDto.Id, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateTask")) {
+        var taskDao = pm.TaskDao;
+        pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskDto.Id);
+          taskDto.CopyToEntity(task);
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public void UpdateTaskData(DT.Task taskDto, DT.TaskData taskDataDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskDto.Id, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateTaskData")) {
+        var taskDao = pm.TaskDao;
+        var taskDataDao = pm.TaskDataDao;
+        pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskDto.Id);
+          var taskData = taskDataDao.GetById(taskDataDto.TaskId);
+          taskDto.CopyToEntity(task);
+          taskDataDto.CopyToEntity(taskData);
+          taskData.LastUpdate = DateTime.Now;
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public DT.Task UpdateTaskState(Guid taskId, DT.TaskState taskState, Guid? slaveId, Guid? userId, string exception) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateTaskState")) {
+        var taskDao = pm.TaskDao;
+        return pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskId);
+          UpdateTaskState(pm, task, taskState, slaveId, userId, exception);
+          pm.SubmitChanges();
+          return task.ToDto();
+        });
+      }
     }
     #endregion
@@ -237,212 +234,303 @@
     #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);
-          }
-        }
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("StopTask")) {
+        var taskDao = pm.TaskDao;
+        pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskId);
+          if (task.State == DA.TaskState.Calculating || task.State == DA.TaskState.Transferring) {
+            task.Command = DA.Command.Stop;
+          } else if (task.State != DA.TaskState.Aborted
+                     && task.State != DA.TaskState.Finished
+                     && task.State != DA.TaskState.Failed) {
+            UpdateTaskState(pm, task, DT.TaskState.Aborted, null, null, string.Empty);
+          }
+          pm.SubmitChanges();
+        });
+      }
     }
 
     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);
-        }
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("PauseTask")) {
+        var taskDao = pm.TaskDao;
+        pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskId);
+          if (task.State == DA.TaskState.Calculating || task.State == DA.TaskState.Transferring) {
+            task.Command = DA.Command.Pause;
+          }
+          UpdateTaskState(pm, task, DT.TaskState.Paused, null, null, string.Empty);
+          pm.SubmitChanges();
+        });
+      }
     }
 
     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);
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      AuthorizationManager.AuthorizeForTask(taskId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("RestartTask")) {
+        var taskDao = pm.TaskDao;
+        pm.UseTransaction(() => {
+          var task = taskDao.GetById(taskId);
+          task.Command = null;
+          UpdateTaskState(pm, task, DT.TaskState.Waiting, null, UserManager.CurrentUserId, string.Empty);
+          pm.SubmitChanges();
+        });
+      }
     }
     #endregion
 
     #region Job Methods
-    public Job GetJob(Guid id) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForJob(id, Permission.Read);
-      return trans.UseTransaction(() => {
-        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<Job> GetJobs() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        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<Job> GetAllJobs() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator);
-      return trans.UseTransaction(() => {
-        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 DT.Job GetJob(Guid id) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(id, DT.Permission.Read);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetJob")) {
+        var jobDao = pm.JobDao;
+        var jobPermissionDao = pm.JobPermissionDao;
+        var taskDao = pm.TaskDao;
+        var currentUserId = UserManager.CurrentUserId;
+        return pm.UseTransaction(() => {
+          var job = jobDao.GetById(id).ToDto();
+          if (job != null) {
+            var statistics = taskDao.GetByJobId(job.Id)
+              .GroupBy(x => x.JobId)
+              .Select(x => new {
+                TotalCount = x.Count(),
+                CalculatingCount = x.Count(y => y.State == DA.TaskState.Calculating),
+                FinishedCount = x.Count(y => CompletedStates.Contains(y.State))
+              }).FirstOrDefault();
+            if (statistics != null) {
+              job.JobCount = statistics.TotalCount;
+              job.CalculatingCount = statistics.CalculatingCount;
+              job.FinishedCount = statistics.FinishedCount;
+            }
+            job.OwnerUsername = UserManager.GetUserById(job.OwnerUserId).UserName;
+            if (currentUserId == job.OwnerUserId) {
+              job.Permission = Permission.Full;
+            } else {
+              var jobPermission = jobPermissionDao.GetByJobAndUserId(job.Id, currentUserId);
+              job.Permission = jobPermission == null ? Permission.NotAllowed : jobPermission.Permission.ToDto();
+            }
+          }
+          return job;
+        });
+      }
+    }
+
+    public IEnumerable<DT.Job> GetJobs() {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetJobs")) {
+        var jobDao = pm.JobDao;
+        var jobPermissionDao = pm.JobPermissionDao;
+        var taskDao = pm.TaskDao;
+        var currentUserId = UserManager.CurrentUserId;
+        return pm.UseTransaction(() => {
+          var jobs = jobDao.GetAll()
+            .Where(x => x.OwnerUserId == currentUserId
+                     || x.JobPermissions.Count(y => y.Permission != DA.Permission.NotAllowed
+                                                 && y.GrantedUserId == currentUserId) > 0)
+            .Select(x => x.ToDto())
+            .ToList();
+          var statistics = taskDao.GetAll()
+              .GroupBy(x => x.JobId)
+              .Select(x => new {
+                x.Key,
+                TotalCount = x.Count(),
+                CalculatingCount = x.Count(y => y.State == DA.TaskState.Calculating),
+                FinishedCount = x.Count(y => CompletedStates.Contains(y.State))
+              })
+              .ToList();
+          foreach (var job in jobs) {
+            var statistic = statistics.FirstOrDefault(x => x.Key == job.Id);
+            if (statistic != null) {
+              job.JobCount = statistic.TotalCount;
+              job.CalculatingCount = statistic.CalculatingCount;
+              job.FinishedCount = statistic.FinishedCount;
+            }
+            job.OwnerUsername = UserManager.GetUserById(job.OwnerUserId).UserName;
+            if (currentUserId == job.OwnerUserId) {
+              job.Permission = Permission.Full;
+            } else {
+              var jobPermission = jobPermissionDao.GetByJobAndUserId(job.Id, currentUserId);
+              job.Permission = jobPermission == null ? Permission.NotAllowed : jobPermission.Permission.ToDto();
+            }
+          }
+          return jobs;
+        });
+      }
+    }
+
+    public Guid AddJob(DT.Job jobDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddJob")) {
+        var jobDao = pm.JobDao;
+        var userPriorityDao = pm.UserPriorityDao;
+        return pm.UseTransaction(() => {
+          jobDto.OwnerUserId = UserManager.CurrentUserId;
+          jobDto.DateCreated = DateTime.Now;
+          var job = jobDao.Save(jobDto.ToEntity());
+          if (userPriorityDao.GetById(jobDto.OwnerUserId) == null) {
+            userPriorityDao.Save(new DA.UserPriority {
+              UserId = jobDto.OwnerUserId,
+              DateEnqueued = jobDto.DateCreated
+            });
+          }
+          pm.SubmitChanges();
+          return job.JobId;
+        });
+      }
+    }
+
+    public void UpdateJob(DT.Job jobDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobDto.Id, DT.Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateJob")) {
+        bool exists = true;
+        var jobDao = pm.JobDao;
+        pm.UseTransaction(() => {
+          var job = jobDao.GetById(jobDto.Id);
+          if (job == null) {
+            exists = false;
+            job = new DA.Job();
+          }
+          jobDto.CopyToEntity(job);
+          if (!exists) {
+            jobDao.Save(job);
+          }
+          pm.SubmitChanges();
+        });
+      }
     }
 
     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
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, DT.Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("DeleteJob")) {
+        var jobDao = pm.JobDao;
+        pm.UseTransaction(() => {
+          // child task will be deleted by db-trigger
+          jobDao.Delete(jobId);
+          pm.SubmitChanges();
+        });
+      }
     }
     #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<FaultReason>(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<FaultReason>(new FaultReason("Not allowed to grant permissions for this experiment"));
-        dao.SetJobPermission(jobId, userManager.CurrentUserId, grantedUserId, DT.Convert.ToEntity(permission));
-      });
+    public void GrantPermission(Guid jobId, Guid grantedUserId, DT.Permission permission) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GrantPermission")) {
+        var jobPermissionDao = pm.JobPermissionDao;
+        var currentUserId = UserManager.CurrentUserId;
+        pm.UseTransaction(() => {
+          jobPermissionDao.SetJobPermission(jobId, currentUserId, grantedUserId, permission.ToEntity());
+          pm.SubmitChanges();
+        });
+      }
     }
 
     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<FaultReason>(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<FaultReason>(new FaultReason("Not allowed to grant permissions for this experiment"));
-        dao.SetJobPermission(jobId, userManager.CurrentUserId, grantedUserId, DA.Permission.NotAllowed);
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("RevokePermission")) {
+        var jobPermissionDao = pm.JobPermissionDao;
+        var currentUserId = UserManager.CurrentUserId;
+        pm.UseTransaction(() => {
+          jobPermissionDao.SetJobPermission(jobId, currentUserId, grantedUserId, DA.Permission.NotAllowed);
+          pm.SubmitChanges();
+        });
+      }
     }
 
     public IEnumerable<JobPermission> 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<FaultReason>(new FaultReason("Not allowed to list permissions for this experiment"));
-        return dao.GetJobPermissions(x => x.JobId == jobId);
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForJob(jobId, Permission.Full);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetJobPermissions")) {
+        var jobPermissionDao = pm.JobPermissionDao;
+        return pm.UseTransaction(() => jobPermissionDao.GetByJobId(jobId)
+          .Select(x => x.ToDto())
+          .ToList()
+        );
+      }
     }
 
     public bool IsAllowedPrivileged() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return authen.IsInRole(HiveRoles.IsAllowedPrivileged);
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      return RoleVerifier.IsInRole(HiveRoles.IsAllowedPrivileged);
     }
     #endregion
 
     #region Login Methods
-    public void Hello(Slave slaveInfo) {
-      authen.AuthenticateForAnyRole(HiveRoles.Slave);
-      if (userManager.CurrentUser.UserName != "hiveslave")
-        slaveInfo.OwnerUserId = userManager.CurrentUserId;
-
-      trans.UseTransaction(() => {
-        var slave = dao.GetSlave(slaveInfo.Id);
-
-        if (slave == null) {
-          dao.AddSlave(slaveInfo);
-        } else {
-          slave.Name = slaveInfo.Name;
-          slave.Description = slaveInfo.Description;
-          slave.OwnerUserId = slaveInfo.OwnerUserId;
-
-          slave.Cores = slaveInfo.Cores;
-          slave.CpuArchitecture = slaveInfo.CpuArchitecture;
-          slave.CpuSpeed = slaveInfo.CpuSpeed;
-          slave.FreeCores = slaveInfo.FreeCores;
-          slave.FreeMemory = slaveInfo.FreeMemory;
-          slave.Memory = slaveInfo.Memory;
-          slave.OperatingSystem = slaveInfo.OperatingSystem;
-
-          slave.LastHeartbeat = DateTime.Now;
-          slave.SlaveState = SlaveState.Idle;
-
-          // don't update those properties: dbSlave.IsAllowedToCalculate, dbSlave.ParentResourceId 
-
-          dao.UpdateSlave(slave);
-        }
-      });
+    public void Hello(DT.Slave slaveInfo) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Slave);
+      if (UserManager.CurrentUser.UserName != "hiveslave") {
+        slaveInfo.OwnerUserId = UserManager.CurrentUserId;
+      }
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("Hello")) {
+        var slaveDao = pm.SlaveDao;
+        pm.UseTransaction(() => {
+          var slave = slaveDao.GetById(slaveInfo.Id);
+          if (slave == null) {
+            slaveDao.Save(slaveInfo.ToEntity());
+          } else {
+            bool oldIsAllowedToCalculate = slave.IsAllowedToCalculate;
+            Guid? oldParentResourceId = slave.ParentResourceId;
+            slaveInfo.CopyToEntity(slave);
+            slave.IsAllowedToCalculate = oldIsAllowedToCalculate;
+            slave.ParentResourceId = oldParentResourceId;
+            slave.LastHeartbeat = DateTime.Now;
+            slave.SlaveState = DA.SlaveState.Idle;
+          }
+          pm.SubmitChanges();
+        });
+      }
     }
 
     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);
-        }
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Slave);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GoodBye")) {
+        var slaveDao = pm.SlaveDao;
+        pm.UseTransaction(() => {
+          var slave = slaveDao.GetById(slaveId);
+          if (slave != null) {
+            slave.SlaveState = DA.SlaveState.Offline;
+            pm.SubmitChanges();
+          }
+        });
+      }
     }
     #endregion
 
     #region Heartbeat Methods
-    public List<MessageContainer> Heartbeat(Heartbeat heartbeat) {
-      authen.AuthenticateForAnyRole(HiveRoles.Slave);
-
+    public List<MessageContainer> Heartbeat(DT.Heartbeat heartbeat) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Slave);
       List<MessageContainer> result = new List<MessageContainer>();
       try {
-        result = heartbeatManager.ProcessHeartbeat(heartbeat);
+        using (new PerformanceLogger("ProcessHeartbeat")) {
+          result = HeartbeatManager.ProcessHeartbeat(heartbeat);
+        }
       }
       catch (Exception ex) {
-        DA.LogFactory.GetLogger(this.GetType().Namespace).Log("Exception processing Heartbeat: " + ex.ToString());
-      }
-
+        DA.LogFactory.GetLogger(this.GetType().Namespace).Log(string.Format("Exception processing Heartbeat: {0}", ex));
+      }
       if (HeuristicLab.Services.Hive.Properties.Settings.Default.TriggerEventManagerInHeartbeat) {
         TriggerEventManager(false);
       }
-
       return result;
     }
@@ -450,64 +538,61 @@
 
     #region Plugin Methods
-    public Guid AddPlugin(Plugin plugin, List<PluginData> pluginDatas) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        plugin.UserId = userManager.CurrentUserId;
+    public DT.Plugin GetPlugin(Guid pluginId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetPlugin")) {
+        var pluginDao = pm.PluginDao;
+        return pm.UseTransaction(() => pluginDao.GetById(pluginId).ToDto());
+      }
+    }
+
+    public Guid AddPlugin(DT.Plugin plugin, List<DT.PluginData> pluginData) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddPlugin")) {
+        var pluginDao = pm.PluginDao;
+        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<PluginAlreadyExistsFault>(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 trans.UseTransaction(() => {
-        return DT.Convert.ToDto(optimizedDao.GetPluginById(pluginId));
-      });
-    }
-
-    public Plugin GetPluginByHash(byte[] hash) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      return trans.UseTransaction(() => {
-        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<Plugin> GetPlugins() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      return trans.UseTransaction(() => {
-        return dao.GetPlugins(x => x.Hash != null);
-      });
-    }
-
-    public IEnumerable<PluginData> GetPluginDatas(List<Guid> pluginIds) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
-      var pluginDatas = new List<PluginData>();
-      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);
-      trans.UseTransaction(() => {
-        dao.DeletePlugin(pluginId);
-      });
+        return pm.UseTransaction(() => {
+          var pluginEntity = pluginDao.GetByHash(plugin.Hash).SingleOrDefault();
+          if (pluginEntity != null) {
+            throw new FaultException<PluginAlreadyExistsFault>(new PluginAlreadyExistsFault(pluginEntity.PluginId));
+          }
+          pluginEntity = plugin.ToEntity();
+          foreach (var data in pluginData) {
+            data.PluginId = default(Guid); // real id will be assigned from linq2sql
+            pluginEntity.PluginData.Add(data.ToEntity());
+          }
+          pluginDao.Save(pluginEntity);
+          pm.SubmitChanges();
+          return pluginEntity.PluginId;
+        });
+      }
+    }
+
+    public IEnumerable<DT.Plugin> GetPlugins() {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetPlugins")) {
+        var pluginDao = pm.PluginDao;
+        return pm.UseTransaction(() => pluginDao.GetAll()
+          .Where(x => x.Hash != null)
+          .Select(x => x.ToDto())
+          .ToList()
+        );
+      }
+    }
+
+    public IEnumerable<DT.PluginData> GetPluginDatas(List<Guid> pluginIds) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetPluginDatas")) {
+        var pluginDataDao = pm.PluginDataDao;
+        return pm.UseTransaction(() => pluginDataDao.GetAll()
+            .Where(x => pluginIds.Contains(x.PluginId))
+            .Select(x => x.ToDto())
+            .ToList()
+        );
+      }
     }
     #endregion
@@ -515,167 +600,268 @@
     #region ResourcePermission Methods
     public void GrantResourcePermissions(Guid resourceId, Guid[] grantedUserIds) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      trans.UseTransaction(() => {
-        Resource resource = dao.GetResource(resourceId);
-        if (resource == null) throw new FaultException<FaultReason>(new FaultReason("Could not find resource with id " + resourceId));
-        if (resource.OwnerUserId != userManager.CurrentUserId && !authen.IsInRole(HiveRoles.Administrator)) throw new FaultException<FaultReason>(new FaultReason("Not allowed to grant permission for this resource"));
-        foreach (Guid id in grantedUserIds)
-          dao.AddResourcePermission(new ResourcePermission { ResourceId = resourceId, GrantedByUserId = userManager.CurrentUserId, GrantedUserId = id });
-      });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GrantResourcePermissions")) {
+        pm.UseTransaction(() => {
+          var resource = AuthorizeForResource(pm, resourceId);
+          var resourcePermissions = resource.ResourcePermissions.ToList();
+          foreach (var id in grantedUserIds) {
+            if (resourcePermissions.All(x => x.GrantedUserId != id)) {
+              resource.ResourcePermissions.Add(new DA.ResourcePermission {
+                GrantedUserId = id,
+                GrantedByUserId = UserManager.CurrentUserId
+              });
+            }
+          }
+          pm.SubmitChanges();
+        });
+      }
     }
 
     public void RevokeResourcePermissions(Guid resourceId, Guid[] grantedUserIds) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      trans.UseTransaction(() => {
-        Resource resource = dao.GetResource(resourceId);
-        if (resource == null) throw new FaultException<FaultReason>(new FaultReason("Could not find resource with id " + resourceId));
-        if (resource.OwnerUserId != userManager.CurrentUserId && !authen.IsInRole(HiveRoles.Administrator)) throw new FaultException<FaultReason>(new FaultReason("Not allowed to revoke permission for this resource"));
-        foreach (Guid id in grantedUserIds)
-          dao.DeleteResourcePermission(resourceId, id);
-      });
-    }
-
-    public IEnumerable<ResourcePermission> GetResourcePermissions(Guid resourceId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        Resource resource = dao.GetResource(resourceId);
-        if (resource == null) throw new FaultException<FaultReason>(new FaultReason("Could not find resource with id " + resourceId));
-        return dao.GetResourcePermissions(x => x.ResourceId == resourceId);
-      });
-    }
-    #endregion
-
-    #region Resource Methods
-    public IEnumerable<Resource> GetChildResources(Guid resourceId) {
-      return trans.UseTransaction(() => { return dao.GetChildResources(resourceId); });
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("RevokeResourcePermissions")) {
+        var resourcePermissionDao = pm.ResourcePermissionDao;
+        pm.UseTransaction(() => {
+          AuthorizeForResource(pm, resourceId);
+          resourcePermissionDao.DeleteByResourceAndGrantedUserId(resourceId, grantedUserIds);
+        });
+      }
+    }
+
+    public IEnumerable<DT.ResourcePermission> GetResourcePermissions(Guid resourceId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetResourcePermissions")) {
+        var resourcePermissionDao = pm.ResourcePermissionDao;
+        return pm.UseTransaction(() => resourcePermissionDao.GetByResourceId(resourceId)
+          .Select(x => x.ToDto())
+          .ToList()
+        );
+      }
     }
     #endregion
 
     #region Slave Methods
+    public Guid AddSlave(DT.Slave slaveDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddSlave")) {
+        var slaveDao = pm.SlaveDao;
+        return pm.UseTransaction(() => {
+          var slave = slaveDao.Save(slaveDto.ToEntity());
+          pm.SubmitChanges();
+          return slave.ResourceId;
+        });
+      }
+    }
+
+    public Guid AddSlaveGroup(DT.SlaveGroup slaveGroupDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddSlaveGroup")) {
+        var slaveGroupDao = pm.SlaveGroupDao;
+        return pm.UseTransaction(() => {
+          if (slaveGroupDto.Id == Guid.Empty) {
+            slaveGroupDto.Id = Guid.NewGuid();
+          }
+          var slaveGroup = slaveGroupDao.Save(slaveGroupDto.ToEntity());
+          pm.SubmitChanges();
+          return slaveGroup.ResourceId;
+        });
+      }
+    }
+
+    public DT.Slave GetSlave(Guid slaveId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetSlave")) {
+        var slaveDao = pm.SlaveDao;
+        return pm.UseTransaction(() => slaveDao.GetById(slaveId).ToDto());
+      }
+    }
+
+    public IEnumerable<DT.Slave> GetSlaves() {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      bool isAdministrator = RoleVerifier.IsInRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetSlaves")) {
+        var slaveDao = pm.SlaveDao;
+        var resourcePermissionDao = pm.ResourcePermissionDao;
+        var currentUserId = UserManager.CurrentUserId;
+        return pm.UseTransaction(() => {
+          var resourcePermissions = resourcePermissionDao.GetAll();
+          return slaveDao.GetAll()
+            .Where(x => isAdministrator
+              || x.OwnerUserId == null
+              || x.OwnerUserId == currentUserId
+              || UserManager.VerifyUser(currentUserId, resourcePermissions
+                  .Where(y => y.ResourceId == x.ResourceId)
+                  .Select(z => z.GrantedUserId)
+                  .ToList())
+              )
+            .Select(x => x.ToDto())
+            .ToList();
+        });
+      }
+    }
+
+    public IEnumerable<DT.SlaveGroup> GetSlaveGroups() {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      bool isAdministrator = RoleVerifier.IsInRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetSlaveGroups")) {
+        var slaveGroupDao = pm.SlaveGroupDao;
+        var resourcePermissionDao = pm.ResourcePermissionDao;
+        var currentUserId = UserManager.CurrentUserId;
+        return pm.UseTransaction(() => {
+          var resourcePermissions = resourcePermissionDao.GetAll();
+          return slaveGroupDao.GetAll()
+            .Where(x => isAdministrator
+              || x.OwnerUserId == null
+              || x.OwnerUserId == currentUserId
+              || UserManager.VerifyUser(currentUserId, resourcePermissions
+                  .Where(y => y.ResourceId == x.ResourceId)
+                  .Select(z => z.GrantedUserId)
+                  .ToList())
+              )
+            .Select(x => x.ToDto())
+            .ToList();
+        });
+      }
+    }
+
+    public void UpdateSlave(DT.Slave slaveDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateSlave")) {
+        var slaveDao = pm.SlaveDao;
+        pm.UseTransaction(() => {
+          var slave = slaveDao.GetById(slaveDto.Id);
+          if (slave != null) {
+            slaveDto.CopyToEntity(slave);
+          } else {
+            slaveDao.Save(slaveDto.ToEntity());
+          }
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public void UpdateSlaveGroup(DT.SlaveGroup slaveGroupDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateSlaveGroup")) {
+        var slaveGroupDao = pm.SlaveGroupDao;
+        pm.UseTransaction(() => {
+          var slaveGroup = slaveGroupDao.GetById(slaveGroupDto.Id);
+          if (slaveGroup != null) {
+            slaveGroupDto.CopyToEntity(slaveGroup);
+          } else {
+            slaveGroupDao.Save(slaveGroupDto.ToEntity());
+          }
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public void DeleteSlave(Guid slaveId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForResourceAdministration(slaveId);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("DeleteSlave")) {
+        var slaveDao = pm.SlaveDao;
+        pm.UseTransaction(() => {
+          slaveDao.Delete(slaveId);
+        });
+      }
+    }
+
+    public void DeleteSlaveGroup(Guid slaveGroupId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForResourceAdministration(slaveGroupId);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("DeleteSlaveGroup")) {
+        var slaveGroupDao = pm.SlaveGroupDao;
+        pm.UseTransaction(() => {
+          slaveGroupDao.Delete(slaveGroupId);
+        });
+      }
+    }
+
+    public void AddResourceToGroup(Guid slaveGroupId, Guid resourceId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddResourceToGroup")) {
+        var resourceDao = pm.ResourceDao;
+        pm.UseTransaction(() => {
+          var resource = resourceDao.GetById(resourceId);
+          resource.ParentResourceId = slaveGroupId;
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public void RemoveResourceFromGroup(Guid slaveGroupId, Guid resourceId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("RemoveResourceFromGroup")) {
+        var resourceDao = pm.ResourceDao;
+        pm.UseTransaction(() => {
+          var resource = resourceDao.GetById(resourceId);
+          resource.ParentResourceId = null;
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public Guid GetResourceId(string resourceName) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetResourceId")) {
+        var resourceDao = pm.ResourceDao;
+        return pm.UseTransaction(() => {
+          var resource = resourceDao.GetByName(resourceName);
+          return resource != null ? resource.ResourceId : Guid.Empty;
+        });
+      }
+    }
+
+    public void TriggerEventManager(bool force) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      // 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;
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("TriggerEventManager")) {
+        cleanup = false;
+        var lifecycleDao = pm.LifecycleDao;
+        pm.UseTransaction(() => {
+          var lastLifecycle = lifecycleDao.GetLastLifecycle();
+          DateTime lastCleanup = lastLifecycle != null ? lastLifecycle.LastCleanup : DateTime.MinValue;
+          if (force || DateTime.Now - lastCleanup > HeuristicLab.Services.Hive.Properties.Settings.Default.CleanupInterval) {
+            lifecycleDao.UpdateLifecycle();
+            cleanup = true;
+            pm.SubmitChanges();
+          }
+        }, true);
+      }
+      if (cleanup) {
+        EventManager.Cleanup();
+      }
+    }
+
     public int GetNewHeartbeatInterval(Guid slaveId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Slave);
-
-      Slave s = trans.UseTransaction(() => { return 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, HiveRoles.Client);
-      return trans.UseTransaction(() => dao.AddSlaveGroup(slaveGroup));
-    }
-
-    public Slave GetSlave(Guid slaveId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator);
-      return trans.UseTransaction(() => { return dao.GetSlave(slaveId); });
-    }
-
-    public SlaveGroup GetSlaveGroup(Guid slaveGroupId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator);
-      return trans.UseTransaction(() => { return dao.GetSlaveGroup(slaveGroupId); });
-    }
-
-    public IEnumerable<Slave> GetSlaves() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        return dao.GetSlaves(x => true).Where(x => x.OwnerUserId == null
-                                           || x.OwnerUserId == userManager.CurrentUserId
-                                           || userManager.VerifyUser(userManager.CurrentUserId, GetResourcePermissions(x.Id).Select(y => y.GrantedUserId).ToList())
-                                           || authen.IsInRole(HiveRoles.Administrator)).ToArray();
-      });
-    }
-
-    public IEnumerable<SlaveGroup> GetSlaveGroups() {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => {
-        return dao.GetSlaveGroups(x => true).Where(x => x.OwnerUserId == null
-                                                || x.OwnerUserId == userManager.CurrentUserId
-                                                || userManager.VerifyUser(userManager.CurrentUserId, GetResourcePermissions(x.Id).Select(y => y.GrantedUserId).ToList())
-                                                || authen.IsInRole(HiveRoles.Administrator)).ToArray();
-      });
-    }
-
-    public void UpdateSlave(Slave slave) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      trans.UseTransaction(() => {
-        dao.UpdateSlave(slave);
-      });
-    }
-
-    public void UpdateSlaveGroup(SlaveGroup slaveGroup) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      trans.UseTransaction(() => {
-        dao.UpdateSlaveGroup(slaveGroup);
-      });
-    }
-
-    public void DeleteSlave(Guid slaveId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForResourceAdministration(slaveId);
-      trans.UseTransaction(() => {
-        dao.DeleteSlave(slaveId);
-      });
-    }
-
-    public void DeleteSlaveGroup(Guid slaveGroupId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForResourceAdministration(slaveGroupId);
-      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();
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Slave);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetNewHeartbeatInterval")) {
+        var slaveDao = pm.SlaveDao;
+        return pm.UseTransaction(() => {
+          var slave = slaveDao.GetById(slaveId);
+          if (slave != null) {
+            return slave.HbInterval;
+          }
+          return -1;
+        });
       }
     }
@@ -683,30 +869,58 @@
 
     #region Downtime Methods
-    public Guid AddDowntime(Downtime downtime) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForResourceAdministration(downtime.ResourceId);
-      return trans.UseTransaction(() => dao.AddDowntime(downtime));
+    public Guid AddDowntime(DT.Downtime downtimeDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForResourceAdministration(downtimeDto.ResourceId);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("AddDowntime")) {
+        var downtimeDao = pm.DowntimeDao;
+        return pm.UseTransaction(() => {
+          var downtime = downtimeDao.Save(downtimeDto.ToEntity());
+          pm.SubmitChanges();
+          return downtime.ResourceId;
+        });
+      }
     }
 
     public void DeleteDowntime(Guid downtimeId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      // TODO: pass resource id
-      // author.AuthorizeForResource(resourceId);
-      trans.UseTransaction(() => {
-        dao.DeleteDowntime(downtimeId);
-      });
-    }
-
-    public void UpdateDowntime(Downtime downtime) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      author.AuthorizeForResourceAdministration(downtime.ResourceId);
-      trans.UseTransaction(() => {
-        dao.UpdateDowntime(downtime);
-      });
-    }
-
-    public IEnumerable<Downtime> GetDowntimesForResource(Guid resourceId) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
-      return trans.UseTransaction(() => dao.GetDowntimes(x => x.ResourceId == resourceId));
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("DeleteDowntime")) {
+        var downtimeDao = pm.DowntimeDao;
+        pm.UseTransaction(() => {
+          downtimeDao.Delete(downtimeId);
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public void UpdateDowntime(DT.Downtime downtimeDto) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      AuthorizationManager.AuthorizeForResourceAdministration(downtimeDto.ResourceId);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("UpdateDowntime")) {
+        var downtimeDao = pm.DowntimeDao;
+        pm.UseTransaction(() => {
+          var downtime = downtimeDao.GetById(downtimeDto.Id);
+          if (downtime != null) {
+            downtimeDto.CopyToEntity(downtime);
+          } else {
+            downtimeDao.Save(downtimeDto.ToEntity());
+          }
+          pm.SubmitChanges();
+        });
+      }
+    }
+
+    public IEnumerable<DT.Downtime> GetDowntimesForResource(Guid resourceId) {
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetDowntimesForResource")) {
+        var downtimeDao = pm.DowntimeDao;
+        return pm.UseTransaction(() => downtimeDao.GetByResourceId(resourceId)
+          .Select(x => x.ToDto())
+          .ToList()
+        );
+      }
     }
     #endregion
@@ -714,48 +928,61 @@
     #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;
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      var user = UserManager.GetUserById(userId);
+      return user != null ? user.UserName : null;
     }
 
     public Guid GetUserIdByUsername(string username) {
-      authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
+      RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
       var user = ServiceLocator.Instance.UserManager.GetUserByName(username);
-      return user != null ? (Guid)user.ProviderUserKey : Guid.Empty;
-    }
-    #endregion
-
-    #region UserPriority Methods
-    public IEnumerable<UserPriority> GetUserPriorities() {
-      return trans.UseTransaction(() => dao.GetUserPriorities(x => true));
-    }
-    #endregion
-
-    #region Helper Methods
-    private IEnumerable<Task> GetChildTasks(Guid? parentTaskId, bool recursive, bool includeParent) {
-      var tasks = new List<Task>(dao.GetTasks(x => parentTaskId == null ? !x.ParentTaskId.HasValue : x.ParentTaskId.Value == parentTaskId));
-
-      if (recursive) {
-        var childs = new List<Task>();
-        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
-
-    #region Statistics Methods
-    public IEnumerable<Statistics> GetStatistics() {
-      return trans.UseTransaction(() => { return dao.GetStatistics(x => true); });
-    }
-    public IEnumerable<Statistics> GetStatisticsForTimePeriod(DateTime from, DateTime to) {
-      return trans.UseTransaction(() => { return dao.GetStatistics(x => x.Timestamp >= from && x.Timestamp <= to); });
+      return user != null ? (Guid?)user.ProviderUserKey ?? Guid.Empty : Guid.Empty;
+    }
+    #endregion
+
+    #region UserPriorities Methods
+    public IEnumerable<DT.UserPriority> GetUserPriorities() {
+      var pm = PersistenceManager;
+      using (new PerformanceLogger("GetUserPriorities")) {
+        var userPriorityDao = pm.UserPriorityDao;
+        return pm.UseTransaction(() => userPriorityDao.GetAll()
+          .Select(x => x.ToDto())
+          .ToList()
+        );
+      }
+    }
+    #endregion
+
+    #region Private Helper Methods
+    private void UpdateTaskState(IPersistenceManager pm, DA.Task task, DT.TaskState taskState, Guid? slaveId, Guid? userId, string exception) {
+      var stateLogDao = pm.StateLogDao;
+      var taskStateEntity = taskState.ToEntity();
+      if (task.Command == DA.Command.Pause && task.State == DA.TaskState.Paused
+          || task.Command == DA.Command.Abort && task.State == DA.TaskState.Aborted
+          || task.Command == DA.Command.Stop && task.State == DA.TaskState.Aborted) {
+        task.Command = null;
+      } else if (taskStateEntity == DA.TaskState.Paused && task.Command != null) {
+        // slave paused and uploaded the task (no user-command) -> set waiting.
+        taskStateEntity = DA.TaskState.Waiting;
+      }
+      stateLogDao.Save(new DA.StateLog {
+        State = taskStateEntity,
+        DateTime = DateTime.Now,
+        TaskId = task.TaskId,
+        UserId = userId,
+        SlaveId = slaveId,
+        Exception = exception
+      });
+      task.State = taskStateEntity;
+    }
+
+    private DA.Resource AuthorizeForResource(IPersistenceManager pm, Guid resourceId) {
+      var resourceDao = pm.ResourceDao;
+      var resource = resourceDao.GetById(resourceId);
+      if (resource == null) throw new SecurityException("Not authorized");
+      if (resource.OwnerUserId != UserManager.CurrentUserId
+          && !RoleVerifier.IsInRole(HiveRoles.Administrator)) {
+        throw new SecurityException("Not authorized");
+      }
+      return resource;
     }
     #endregion
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 12878)
@@ -0,0 +1,396 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Linq;
+using HeuristicLab.Services.Access.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Manager;
+
+namespace HeuristicLab.Services.Hive {
+  public class HiveStatisticsGenerator : IStatisticsGenerator {
+
+    private const string UnknownUserName = "Unknown";
+    private static readonly TimeSpan SmallestTimeSpan = new TimeSpan(0, 5, 0);
+    private static readonly TaskState[] CompletedStates = { TaskState.Finished, TaskState.Aborted, TaskState.Failed };
+
+    public void GenerateStatistics() {
+      using (var pm = new PersistenceManager()) {
+        pm.UseTransaction(() => {
+          UpdateDimUserTable(pm);
+          UpdateDimJobTable(pm);
+          UpdateDimClientsTable(pm);
+          pm.SubmitChanges();
+        });
+
+        DimTime time = null;
+        pm.UseTransaction(() => {
+          time = UpdateDimTimeTable(pm);
+          pm.SubmitChanges();
+        });
+
+        if (time != null) {
+          pm.UseTransaction(() => {
+            UpdateFactClientInfoTable(time, pm);
+            pm.SubmitChanges();
+          });
+
+          pm.UseTransaction(() => {
+            UpdateTaskFactsTable(time, pm);
+            try {
+              pm.SubmitChanges();
+              UpdateExistingDimJobs(pm);
+              pm.SubmitChanges();
+            }
+            catch (DuplicateKeyException e) {
+              var logger = LogFactory.GetLogger(typeof(HiveStatisticsGenerator).Namespace);
+              logger.Log(string.Format(
+                @"Propable change from summertime to wintertime, resulting in overlapping times.
+                          On wintertime to summertime change, slave timeouts and a fact gap will occur. 
+                          Exception Details: {0}", e));
+            }
+          });
+        }
+      }
+    }
+
+    private DimTime UpdateDimTimeTable(PersistenceManager pm) {
+      var dimTimeDao = pm.DimTimeDao;
+      var now = DateTime.Now;
+      var timeEntry = new DimTime {
+        Time = now,
+        Minute = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0),
+        Hour = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0),
+        Day = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0),
+        Month = new DateTime(now.Year, now.Month, 1, 0, 0, 0),
+        Year = new DateTime(now.Year, 1, 1, 0, 0, 0)
+      };
+      return dimTimeDao.Save(timeEntry);
+    }
+
+    private void UpdateDimUserTable(PersistenceManager pm) {
+      var dimUserDao = pm.DimUserDao;
+      var resourceDao = pm.ResourceDao;
+      var jobDao = pm.JobDao;
+      var existingUserIds = dimUserDao.GetAll().Select(x => x.UserId);
+      var vaildResourceOwnerIds = resourceDao.GetResourcesWithValidOwner().Select(x => x.OwnerUserId.Value);
+      var jobOwnerIds = jobDao.GetAll().Select(x => x.OwnerUserId);
+      var newUserIds = vaildResourceOwnerIds
+        .Union(jobOwnerIds)
+        .Where(id => !existingUserIds.Contains(id))
+        .ToList();
+      dimUserDao.Save(newUserIds.Select(x => new DimUser {
+        UserId = x,
+        Name = GetUserName(x)
+      }));
+    }
+
+    private void UpdateDimJobTable(PersistenceManager pm) {
+      var dimJobDao = pm.DimJobDao;
+      var jobDao = pm.JobDao;
+      var taskDao = pm.TaskDao;
+      var dimJobIds = dimJobDao.GetAll().Select(x => x.JobId);
+      var newJobs = jobDao.GetAll()
+        .Where(x => !dimJobIds.Contains(x.JobId))
+        .Select(x => new {
+          JobId = x.JobId,
+          UserId = x.OwnerUserId,
+          JobName = x.Name ?? string.Empty,
+          DateCreated = x.DateCreated,
+          TotalTasks = taskDao.GetAll().Count(y => y.JobId == x.JobId)
+        })
+        .ToList();
+      dimJobDao.Save(newJobs.Select(x => new DimJob {
+        JobId = x.JobId,
+        JobName = x.JobName,
+        UserId = x.UserId,
+        UserName = GetUserName(x.UserId),
+        DateCreated = x.DateCreated,
+        TotalTasks = x.TotalTasks,
+        CompletedTasks = 0,
+        DateCompleted = null
+      }));
+    }
+
+    private void UpdateExistingDimJobs(PersistenceManager pm) {
+      var jobDao = pm.JobDao;
+      var dimJobDao = pm.DimJobDao;
+      var factTaskDao = pm.FactTaskDao;
+      foreach (var dimJob in dimJobDao.GetNotCompletedJobs()) {
+        var taskStates = factTaskDao.GetByJobId(dimJob.JobId)
+            .GroupBy(x => x.TaskState)
+            .Select(x => new {
+              State = x.Key,
+              Count = x.Count()
+            }).ToList();
+        int totalTasks = 0, completedTasks = 0;
+        foreach (var state in taskStates) {
+          totalTasks += state.Count;
+          if (CompletedStates.Contains(state.State)) {
+            completedTasks += state.Count;
+          }
+        }
+        if (totalTasks == completedTasks) {
+          var completeDate = factTaskDao.GetLastCompletedTaskFromJob(dimJob.JobId);
+          if (completeDate == null) {
+            if (jobDao.GetById(dimJob.JobId) == null) {
+              completeDate = DateTime.Now;
+            }
+          }
+          dimJob.DateCompleted = completeDate;
+        }
+        dimJob.TotalTasks = totalTasks;
+        dimJob.CompletedTasks = completedTasks;
+      }
+    }
+
+    private void UpdateDimClientsTable(PersistenceManager pm) {
+      var dimClientDao = pm.DimClientDao;
+      var slaveDao = pm.SlaveDao;
+      var slaves = slaveDao.GetAll();
+      var recentlyAddedClients = dimClientDao.GetActiveClients();
+      var slaveIds = slaves.Select(x => x.ResourceId);
+
+      var removedClientIds = recentlyAddedClients
+        .Where(x => !slaveIds.Contains(x.ResourceId))
+        .Select(x => x.Id);
+      var modifiedClients =
+        from slave in slaves
+        join client in recentlyAddedClients on slave.ResourceId equals client.ResourceId
+        where (slave.Name != client.Name
+               || slave.ParentResourceId == null && client.ResourceGroupId != null // because both can be null and null comparison
+               || slave.ParentResourceId != null && client.ResourceGroupId == null // does return no entry on the sql server
+               || slave.ParentResourceId != client.ResourceGroupId
+               || ((slave.ParentResource != null) && slave.ParentResource.ParentResourceId != client.ResourceGroup2Id))
+        select new {
+          SlaveId = slave.ResourceId,
+          ClientId = client.Id
+        };
+      var clientIds = dimClientDao.GetActiveClients().Select(x => x.ResourceId);
+      var modifiedClientIds = modifiedClients.Select(x => x.SlaveId);
+      var newClients = slaves
+        .Where(x => !clientIds.Contains(x.ResourceId)
+                    || modifiedClientIds.Contains(x.ResourceId))
+        .Select(x => new {
+          x.ResourceId,
+          x.Name,
+          ResourceGroupId = x.ParentResourceId,
+          GroupName = x.ParentResource != null ? x.ParentResource.Name : null,
+          ResourceGroup2Id = x.ParentResource != null ? x.ParentResource.ParentResourceId : null,
+          GroupName2 = x.ParentResource != null ? x.ParentResource.ParentResource != null ? x.ParentResource.ParentResource.Name : null : null
+        })
+        .ToList();
+
+      var clientsToUpdate = removedClientIds.Union(modifiedClients.Select(x => x.ClientId));
+      dimClientDao.UpdateExpirationTime(clientsToUpdate, DateTime.Now);
+      dimClientDao.Save(newClients.Select(x => new DimClient {
+        ResourceId = x.ResourceId,
+        Name = x.Name,
+        ExpirationTime = null,
+        ResourceGroupId = x.ResourceGroupId,
+        GroupName = x.GroupName,
+        ResourceGroup2Id = x.ResourceGroup2Id,
+        GroupName2 = x.GroupName2
+      }));
+    }
+
+    private void UpdateFactClientInfoTable(DimTime newTime, PersistenceManager pm) {
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var slaveDao = pm.SlaveDao;
+      var dimClientDao = pm.DimClientDao;
+
+      var newRawFactInfos =
+        from s in slaveDao.GetAll()
+        join c in dimClientDao.GetActiveClients() on s.ResourceId equals c.ResourceId
+        join lcf in factClientInfoDao.GetLastUpdateTimestamps() on c.ResourceId equals lcf.ResourceId into joinCf
+        from cf in joinCf.DefaultIfEmpty()
+        select new {
+          ClientId = c.Id,
+          UserId = s.OwnerUserId ?? Guid.Empty,
+          TotalCores = s.Cores ?? 0,
+          FreeCores = s.FreeCores ?? 0,
+          TotalMemory = s.Memory ?? 0,
+          FreeMemory = s.FreeMemory ?? 0,
+          CpuUtilization = s.CpuUtilization,
+          SlaveState = s.SlaveState,
+          IsAllowedToCalculate = s.IsAllowedToCalculate,
+          LastFactTimestamp = cf.Timestamp
+        };
+
+      factClientInfoDao.Save(
+        from x in newRawFactInfos.ToList()
+        let duration = x.LastFactTimestamp != null
+                       ? (int)(newTime.Time - (DateTime)x.LastFactTimestamp).TotalSeconds
+                       : (int)SmallestTimeSpan.TotalSeconds
+        select new FactClientInfo {
+          ClientId = x.ClientId,
+          DimTime = newTime,
+          UserId = x.UserId,
+          NumUsedCores = x.TotalCores - x.FreeCores,
+          NumTotalCores = x.TotalCores,
+          UsedMemory = x.TotalMemory - x.FreeMemory,
+          TotalMemory = x.TotalMemory,
+          CpuUtilization = Math.Round(x.CpuUtilization, 2),
+          SlaveState = x.SlaveState,
+          IdleTime = x.SlaveState == SlaveState.Idle && x.IsAllowedToCalculate ? duration : 0,
+          UnavailableTime = x.SlaveState == SlaveState.Idle && !x.IsAllowedToCalculate ? duration : 0,
+          OfflineTime = x.SlaveState == SlaveState.Offline ? duration : 0,
+          IsAllowedToCalculate = x.IsAllowedToCalculate
+        }
+      );
+    }
+
+    private void UpdateTaskFactsTable(DimTime newTime, PersistenceManager pm) {
+      var factTaskDao = pm.FactTaskDao;
+      var taskDao = pm.TaskDao;
+      var dimClientDao = pm.DimClientDao;
+
+      var factTaskIds = factTaskDao.GetAll().Select(x => x.TaskId);
+      var notFinishedFactTasks = factTaskDao.GetNotFinishedTasks().Select(x => new {
+        x.TaskId,
+        x.LastClientId
+      });
+
+      var newTasks =
+        from task in taskDao.GetAllChildTasks()
+        let stateLogs = task.StateLogs.OrderByDescending(x => x.DateTime)
+        let lastSlaveId = stateLogs.First(x => x.SlaveId != null).SlaveId
+        where (!factTaskIds.Contains(task.TaskId)
+               || notFinishedFactTasks.Select(x => x.TaskId).Contains(task.TaskId))
+        join lastFactTask in notFinishedFactTasks on task.TaskId equals lastFactTask.TaskId into lastFactPerTask
+        from lastFact in lastFactPerTask.DefaultIfEmpty()
+        join client in dimClientDao.GetActiveClients() on lastSlaveId equals client.ResourceId into clientsPerSlaveId
+        from client in clientsPerSlaveId.DefaultIfEmpty()
+        select new {
+          TaskId = task.TaskId,
+          JobId = task.JobId,
+          Priority = task.Priority,
+          CoresRequired = task.CoresNeeded,
+          MemoryRequired = task.MemoryNeeded,
+          State = task.State,
+          StateLogs = stateLogs.OrderBy(x => x.DateTime),
+          LastClientId = client != null
+                         ? client.Id : lastFact != null
+                         ? lastFact.LastClientId : (Guid?)null
+        };
+      factTaskDao.Save(
+        from x in newTasks.ToList()
+        let taskData = CalculateFactTaskData(x.StateLogs)
+        select new FactTask {
+          TaskId = x.TaskId,
+          JobId = x.JobId,
+          StartTime = taskData.StartTime,
+          EndTime = taskData.EndTime,
+          LastClientId = x.LastClientId,
+          Priority = x.Priority,
+          CoresRequired = x.CoresRequired,
+          MemoryRequired = x.MemoryRequired,
+          NumCalculationRuns = taskData.CalculationRuns,
+          NumRetries = taskData.Retries,
+          WaitingTime = taskData.WaitingTime,
+          CalculatingTime = taskData.CalculatingTime,
+          TransferTime = taskData.TransferTime,
+          TaskState = x.State,
+          Exception = taskData.Exception,
+          InitialWaitingTime = taskData.InitialWaitingTime
+        });
+      factTaskDao.Delete(notFinishedFactTasks.Select(x => x.TaskId));
+    }
+
+    private string GetUserName(Guid userId) {
+      try {
+        // we cannot use the ServiceLocator.Instance.UserManager since the janitor service
+        // is not hosted in the iis the MemberShip.GetUser method causes exceptions
+        // needs to be further investigated current workaround: use the authenticationcontext
+        // we could also connect to the access service to get the user name
+        using (ASPNETAuthenticationDataContext dc = new ASPNETAuthenticationDataContext()) {
+          var user = dc.aspnet_Users.SingleOrDefault(x => x.UserId == userId);
+          return user != null ? user.UserName : UnknownUserName;
+        }
+      }
+      catch (Exception) {
+        return UnknownUserName;
+      }
+    }
+
+    private class FactTaskData {
+      public int CalculationRuns { get; set; }
+      public int Retries { get; set; }
+      public long CalculatingTime { get; set; }
+      public long WaitingTime { get; set; }
+      public long TransferTime { get; set; }
+      public long InitialWaitingTime { get; set; }
+      public string Exception { get; set; }
+      public DateTime? StartTime { get; set; }
+      public DateTime? EndTime { get; set; }
+    }
+
+    private FactTaskData CalculateFactTaskData(IEnumerable<StateLog> stateLogs) {
+      var factTaskData = new FactTaskData();
+      var enumerator = stateLogs.GetEnumerator();
+      if (enumerator.MoveNext()) {
+        StateLog current = enumerator.Current, first = current, prev = null;
+        while (current != null) {
+          var next = enumerator.MoveNext() ? enumerator.Current : null;
+          int timeSpanInSeconds;
+          if (next != null) {
+            timeSpanInSeconds = (int)(next.DateTime - current.DateTime).TotalSeconds;
+          } else {
+            timeSpanInSeconds = (int)(DateTime.Now - current.DateTime).TotalSeconds;
+            factTaskData.Exception = current.Exception;
+          }
+          switch (current.State) {
+            case TaskState.Calculating:
+              factTaskData.CalculatingTime += timeSpanInSeconds;
+              factTaskData.CalculationRuns++;
+              if (factTaskData.CalculationRuns == 1) {
+                factTaskData.StartTime = current.DateTime;
+                factTaskData.InitialWaitingTime = (int)(current.DateTime - first.DateTime).TotalSeconds;
+              }
+              if (prev != null && prev.State != TaskState.Transferring) {
+                factTaskData.Retries++;
+              }
+              break;
+
+            case TaskState.Waiting:
+              factTaskData.WaitingTime += timeSpanInSeconds;
+              break;
+
+            case TaskState.Transferring:
+              factTaskData.TransferTime += timeSpanInSeconds;
+              break;
+
+            case TaskState.Finished:
+            case TaskState.Failed:
+            case TaskState.Aborted:
+              factTaskData.EndTime = current.DateTime;
+              break;
+          }
+          prev = current;
+          current = next;
+        }
+      }
+      return factTaskData;
+    }
+  }
+}
Index: unk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IHiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IHiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,166 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq.Expressions;
-using HeuristicLab.Services.Hive.DataAccess;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
-
-namespace HeuristicLab.Services.Hive {
-  public interface IHiveDao {
-    #region Task Methods
-    DT.Task GetTask(Guid id);
-    IEnumerable<DT.Task> GetTasks(Expression<Func<Task, bool>> predicate);
-    IEnumerable<DT.LightweightTask> GetLightweightTasks(Expression<Func<Task, bool>> predicate);
-    IEnumerable<DT.LightweightTask> GetLightweightTasksWithoutStateLog(Expression<Func<Task, bool>> predicate);
-    Guid AddTask(DT.Task dto);
-    void UpdateTaskAndPlugins(DT.Task dto);
-    void UpdateTaskAndStateLogs(DT.Task dto);
-    void UpdateTask(DT.Task dto);
-    void DeleteTask(Guid id);
-    IEnumerable<TaskInfoForScheduler> GetWaitingTasks(DT.Slave slave);
-    IEnumerable<DT.Task> GetParentTasks(IEnumerable<Guid> resourceIds, int count, bool finished);
-    DT.Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception);
-    #endregion
-
-    #region TaskData Methods
-    DT.TaskData GetTaskData(Guid id);
-    IEnumerable<DT.TaskData> GetTaskDatas(Expression<Func<TaskData, bool>> predicate);
-    Guid AddTaskData(DT.TaskData dto);
-    void UpdateTaskData(DT.TaskData dto);
-    void DeleteTaskData(Guid id);
-    #endregion
-
-    #region StateLog Methods
-    DT.StateLog GetStateLog(Guid id);
-    IEnumerable<DT.StateLog> GetStateLogs(Expression<Func<StateLog, bool>> predicate);
-    Guid AddStateLog(DT.StateLog dto);
-    void UpdateStateLog(DT.StateLog dto);
-    void DeleteStateLog(Guid id);
-    #endregion
-
-    #region Job Methods
-    DT.Job GetJob(Guid id);
-    IEnumerable<DT.Job> GetJobs(Expression<Func<Job, bool>> predicate);
-    IEnumerable<JobInfoForScheduler> GetJobInfoForScheduler(Expression<Func<Job, bool>> predicate);
-    Guid AddJob(DT.Job dto);
-    void UpdateJob(DT.Job dto);
-    void DeleteJob(Guid id);
-    #endregion
-
-    #region JobPermission Methods
-    DT.JobPermission GetJobPermission(Guid jobId, Guid grantedUserId);
-    IEnumerable<DT.JobPermission> GetJobPermissions(Expression<Func<JobPermission, bool>> predicate);
-    void AddJobPermission(DT.JobPermission dto);
-    void UpdateJobPermission(DT.JobPermission dto);
-    void DeleteJobPermission(Guid jobId, Guid grantedUserId);
-    void SetJobPermission(Guid jobId, Guid grantedByUserId, Guid grantedUserId, Permission permission);
-    #endregion
-
-    #region Plugin Methods
-    DT.Plugin GetPlugin(Guid id);
-    IEnumerable<DT.Plugin> GetPlugins(Expression<Func<Plugin, bool>> predicate);
-    Guid AddPlugin(DT.Plugin dto);
-    void UpdatePlugin(DT.Plugin dto);
-    void DeletePlugin(Guid id);
-    #endregion
-
-    #region PluginData Methods
-    DT.PluginData GetPluginData(Guid id);
-    IEnumerable<DT.PluginData> GetPluginDatas(Expression<Func<PluginData, bool>> predicate);
-    Guid AddPluginData(DT.PluginData dto);
-    void UpdatePluginData(DT.PluginData dto);
-    void DeletePluginData(Guid id);
-    #endregion
-
-    #region Slave Methods
-    DT.Slave GetSlave(Guid id);
-    IEnumerable<DT.Slave> GetSlaves(Expression<Func<Slave, bool>> predicate);
-    Guid AddSlave(DT.Slave dto);
-    void UpdateSlave(DT.Slave dto);
-    void DeleteSlave(Guid id);
-    #endregion
-
-    #region SlaveGroup Methods
-    DT.SlaveGroup GetSlaveGroup(Guid id);
-    IEnumerable<DT.SlaveGroup> GetSlaveGroups(Expression<Func<SlaveGroup, bool>> predicate);
-    Guid AddSlaveGroup(DT.SlaveGroup dto);
-    void UpdateSlaveGroup(DT.SlaveGroup dto);
-    void DeleteSlaveGroup(Guid id);
-    #endregion
-
-    #region Resource Methods
-    DT.Resource GetResource(Guid id);
-    IEnumerable<DT.Resource> GetResources(Expression<Func<Resource, bool>> predicate);
-    Guid AddResource(DT.Resource dto);
-    void UpdateResource(DT.Resource dto);
-    void DeleteResource(Guid id);
-    void AssignJobToResource(Guid taskId, IEnumerable<Guid> resourceIds);
-    IEnumerable<DT.Resource> GetAssignedResources(Guid jobId);
-    IEnumerable<DT.Resource> GetParentResources(Guid resourceId);
-    IEnumerable<DT.Resource> GetChildResources(Guid resourceId);
-    IEnumerable<DT.Task> GetJobsByResourceId(Guid resourceId);
-    #endregion
-
-    #region ResourcePermission Methods
-    DT.ResourcePermission GetResourcePermission(Guid resourceId, Guid grantedUserId);
-    IEnumerable<DT.ResourcePermission> GetResourcePermissions(Expression<Func<ResourcePermission, bool>> predicate);
-    void AddResourcePermission(DT.ResourcePermission dto);
-    void UpdateResourcePermission(DT.ResourcePermission dto);
-    void DeleteResourcePermission(Guid resourceId, Guid grantedUserId);
-    #endregion
-
-    #region Authorization Methods
-    Permission GetPermissionForTask(Guid taskId, Guid userId);
-    Permission GetPermissionForJob(Guid jobId, Guid userId);
-    Guid GetJobForTask(Guid taskId);
-    #endregion
-
-    #region Lifecycle Methods
-    DateTime GetLastCleanup();
-    void SetLastCleanup(DateTime datetime);
-    #endregion
-
-    #region Downtime Methods
-    DT.Downtime GetDowntime(Guid id);
-    IEnumerable<DT.Downtime> GetDowntimes(Expression<Func<Downtime, bool>> predicate);
-    Guid AddDowntime(DT.Downtime dto);
-    void UpdateDowntime(DT.Downtime dto);
-    void DeleteDowntime(Guid id);
-    #endregion
-
-    #region Statistics Methods
-    Dictionary<Guid, int> GetWaitingTasksByUserForResources(List<Guid> resourceIds);
-    Dictionary<Guid, int> GetCalculatingTasksByUserForResources(List<Guid> resourceIds);
-    DT.Statistics GetStatistic(Guid id);
-    IEnumerable<DT.Statistics> GetStatistics(Expression<Func<Statistics, bool>> predicate);
-    Guid AddStatistics(DT.Statistics dto);
-    void DeleteStatistics(Guid id);
-    List<DT.UserStatistics> GetUserStatistics();
-    #endregion
-
-    #region UserPriority Methods
-    IEnumerable<DT.UserPriority> GetUserPriorities(Expression<Func<UserPriority, bool>> predicate);
-    void EnqueueUserPriority(DT.UserPriority userPriority);
-    #endregion
-  }
-}
Index: unk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IOptimizedHiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IOptimizedHiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,80 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using HeuristicLab.Services.Hive.DataAccess;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
-namespace HeuristicLab.Services.Hive {
-  public interface IOptimizedHiveDao {
-    #region Task Methods
-    Task GetTaskById(Guid task);
-    Task GetTaskByDto(DT.Task taskDto);
-    Tuple<Task, Guid?> GetTaskByIdAndLastStateLogSlaveId(Guid taskId);
-
-    IEnumerable<TaskInfoForScheduler> GetWaitingTasks(Slave slave);
-    IQueryable<DT.LightweightTask> GetLightweightTasks(Guid jobId);
-
-    void UpdateTask(Task task);
-    Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception);
-
-    Guid AddTask(Task task);
-    void AssignJobToResource(Guid taskId, IEnumerable<Guid> resourceIds);
-
-    bool TaskIsAllowedToBeCalculatedBySlave(Guid taskId, Guid slaveId);
-    #endregion
-
-    #region TaskData Methods
-    TaskData GetTaskDataByDto(DT.TaskData taskDataDto);
-    void UpdateTaskData(TaskData taskData);
-    #endregion
-
-    #region Plugin Methods
-    Plugin GetPluginById(Guid pluginId);
-    #endregion
-
-    #region Slave Methods
-    Slave GetSlaveById(Guid id);
-
-    void UpdateSlave(Slave slave);
-
-    bool SlaveHasToShutdownComputer(Guid slaveId);
-    bool SlaveIsAllowedToCalculate(Guid slaveId);
-    #endregion
-
-    #region Resource Methods
-    IEnumerable<Guid> GetAssignedResourceIds(Guid jobId);
-    #endregion
-
-    #region Website Methods
-
-    IEnumerable<Guid> GetAllResourceIds();
-
-    int GetNumberOfWaitingTasks();
-
-    Dictionary<Guid, int> GetCalculatingTasksByUser();
-
-    Dictionary<Guid, int> GetWaitingTasksByUser();
-
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IServiceLocator.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IServiceLocator.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IServiceLocator.cs	(revision 12878)
@@ -20,5 +20,6 @@
 #endregion
 
-using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using HeuristicLab.Services.Hive.Manager;
 
 namespace HeuristicLab.Services.Hive {
@@ -26,8 +27,7 @@
     Access.IRoleVerifier RoleVerifier { get; }
     IAuthorizationManager AuthorizationManager { get; }
-    IHiveDao HiveDao { get; }
-    IOptimizedHiveDao OptimizedHiveDao { get; }
+    IPersistenceManager PersistenceManager { get; }
     IEventManager EventManager { get; }
-    ITransactionManager TransactionManager { get; }
+    IStatisticsGenerator StatisticsGenerator { get; }
     Access.IUserManager UserManager { get; }
     HeartbeatManager HeartbeatManager { get; }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IStatisticsGenerator.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IStatisticsGenerator.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Interfaces/IStatisticsGenerator.cs	(revision 12878)
@@ -0,0 +1,26 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.Hive {
+  public interface IStatisticsGenerator {
+    void GenerateStatistics();
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/AuthorizationManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/AuthorizationManager.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/AuthorizationManager.cs	(revision 12878)
@@ -22,5 +22,8 @@
 using System;
 using System.Security;
+using HeuristicLab.Services.Access;
 using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
 using DT = HeuristicLab.Services.Hive.DataTransfer;
 
@@ -28,27 +31,72 @@
 namespace HeuristicLab.Services.Hive {
   public class AuthorizationManager : IAuthorizationManager {
+
+    private const string NOT_AUTHORIZED = "Current user is not authorized to access the requested resource";
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
+      get { return ServiceLocator.Instance.RoleVerifier; }
+    }
+
     public void Authorize(Guid userId) {
       if (userId != ServiceLocator.Instance.UserManager.CurrentUserId)
-        throw new SecurityException("Current user is not authorized to access object");
+        throw new SecurityException(NOT_AUTHORIZED);
     }
 
     public void AuthorizeForTask(Guid taskId, DT.Permission requiredPermission) {
       if (ServiceLocator.Instance.RoleVerifier.IsInRole(HiveRoles.Slave)) return; // slave-users can access all tasks
-
-      Permission permission = ServiceLocator.Instance.HiveDao.GetPermissionForTask(taskId, ServiceLocator.Instance.UserManager.CurrentUserId);
-      if (permission == Permission.NotAllowed || (permission != DT.Convert.ToEntity(requiredPermission) && DT.Convert.ToEntity(requiredPermission) == Permission.Full))
-        throw new SecurityException("Current user is not authorized to access task");
+      var pm = PersistenceManager;
+      var taskDao = pm.TaskDao;
+      pm.UseTransaction(() => {
+        var task = taskDao.GetById(taskId);
+        if (task == null) throw new SecurityException(NOT_AUTHORIZED);
+        AuthorizeJob(pm, task.JobId, requiredPermission);
+      });
     }
 
     public void AuthorizeForJob(Guid jobId, DT.Permission requiredPermission) {
-      Permission permission = ServiceLocator.Instance.HiveDao.GetPermissionForJob(jobId, ServiceLocator.Instance.UserManager.CurrentUserId);
-      if (permission == Permission.NotAllowed || (permission != DT.Convert.ToEntity(requiredPermission) && DT.Convert.ToEntity(requiredPermission) == Permission.Full))
-        throw new SecurityException("Current user is not authorized to access task");
+      var pm = PersistenceManager;
+      pm.UseTransaction(() => {
+        AuthorizeJob(pm, jobId, requiredPermission);
+      });
     }
 
     public void AuthorizeForResourceAdministration(Guid resourceId) {
-      Resource resource = DT.Convert.ToEntity(ServiceLocator.Instance.HiveDao.GetResource(resourceId));
-      if (resource.OwnerUserId != ServiceLocator.Instance.UserManager.CurrentUserId && !ServiceLocator.Instance.RoleVerifier.IsInRole(HiveRoles.Administrator))
-        throw new SecurityException("Current user is not authorized to access resource");
+      var pm = PersistenceManager;
+      var resourceDao = pm.ResourceDao;
+      pm.UseTransaction(() => {
+        var resource = resourceDao.GetById(resourceId);
+        if (resource == null) throw new SecurityException(NOT_AUTHORIZED);
+        if (resource.OwnerUserId != UserManager.CurrentUserId
+            && !RoleVerifier.IsInRole(HiveRoles.Administrator)) {
+          throw new SecurityException(NOT_AUTHORIZED);
+        }
+      });
+    }
+
+    private DA.Permission GetPermissionForJob(IPersistenceManager pm, Guid jobId, Guid userId) {
+      var jobDao = pm.JobDao;
+      var jobPermissionDao = pm.JobPermissionDao;
+      var job = jobDao.GetById(jobId);
+      if (job == null) return DA.Permission.NotAllowed;
+      if (job.OwnerUserId == userId) return DA.Permission.Full;
+      var jobPermission = jobPermissionDao.GetByJobAndUserId(jobId, userId);
+      if (jobPermission == null) return DA.Permission.NotAllowed;
+      return jobPermission.Permission;
+    }
+
+    private void AuthorizeJob(IPersistenceManager pm, Guid jobId, DT.Permission requiredPermission) {
+      var requiredPermissionEntity = requiredPermission.ToEntity();
+      DA.Permission permission = GetPermissionForJob(pm, jobId, UserManager.CurrentUserId);
+      if (permission == Permission.NotAllowed
+          || ((permission != requiredPermissionEntity) && requiredPermissionEntity == Permission.Full)) {
+        throw new SecurityException(NOT_AUTHORIZED);
+      }
     }
   }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs	(revision 12878)
@@ -21,63 +21,28 @@
 
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using HeuristicLab.Services.Hive.DataAccess;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
 
-
-namespace HeuristicLab.Services.Hive {
-  /// <summary>
-  /// This class offers methods for cleaning up offline slaves and task
-  /// </summary>
+namespace HeuristicLab.Services.Hive.Manager {
   public class EventManager : IEventManager {
-    private IHiveDao dao {
-      get { return ServiceLocator.Instance.HiveDao; }
-    }
-    private IAuthorizationManager auth {
-      get { return ServiceLocator.Instance.AuthorizationManager; }
-    }
-    private ILogger log {
-      get { return LogFactory.GetLogger(this.GetType().Namespace); }
-    }
-    private DataAccess.ITransactionManager trans {
-      get { return ServiceLocator.Instance.TransactionManager; }
+    private const string SlaveTimeout = "Slave timed out.";
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
     }
 
     public void Cleanup() {
-      trans.UseTransaction(() => {
-        SetTimeoutSlavesOffline();
-        SetTimeoutTasksWaiting();
-        DeleteObsoleteSlaves();
+      var pm = PersistenceManager;
+      pm.UseTransaction(() => {
+        SetTimeoutSlavesOffline(pm);
+        SetTimeoutTasksWaiting(pm);
+        DeleteObsoleteSlaves(pm);
+        pm.SubmitChanges();
       });
 
-      trans.UseTransaction(() => {
-        FinishParentTasks();
-        UpdateStatistics();
+      pm.UseTransaction(() => {
+        FinishParentTasks(pm);
+        pm.SubmitChanges();
       });
-    }
-
-    private void UpdateStatistics() {
-      var slaves = dao.GetSlaves(x => x.SlaveState == SlaveState.Calculating || x.SlaveState == SlaveState.Idle);
-
-      var stats = new DataTransfer.Statistics();
-      stats.TimeStamp = DateTime.Now;
-      var slaveStats = new List<DT.SlaveStatistics>();
-      foreach (var slave in slaves) {
-        slaveStats.Add(new DT.SlaveStatistics() {
-          SlaveId = slave.Id,
-          Cores = slave.Cores.HasValue ? slave.Cores.Value : 0,
-          FreeCores = slave.FreeCores.HasValue ? slave.FreeCores.Value : 0,
-          Memory = slave.Memory.HasValue ? slave.Memory.Value : 0,
-          FreeMemory = slave.FreeMemory.HasValue ? slave.FreeMemory.Value : 0,
-          CpuUtilization = slave.CpuUtilization
-        });
-      }
-      stats.SlaveStatistics = slaveStats;
-      //collecting user statistics slows down the db and results in timeouts. 
-      //we have to find another way to deal with this.  
-      //until then the next line is commented out...
-      //stats.UserStatistics = dtoDao.GetUserStatistics();
-      dao.AddStatistics(stats);
     }
 
@@ -85,10 +50,12 @@
     /// Searches for slaves which are timed out, puts them and their task offline
     /// </summary>
-    private void SetTimeoutSlavesOffline() {
-      var slaves = dao.GetSlaves(x => x.SlaveState != SlaveState.Offline);
-      foreach (DT.Slave slave in slaves) {
-        if (!slave.LastHeartbeat.HasValue || (DateTime.Now - slave.LastHeartbeat.Value) > HeuristicLab.Services.Hive.Properties.Settings.Default.SlaveHeartbeatTimeout) {
-          slave.SlaveState = DT.SlaveState.Offline;
-          dao.UpdateSlave(slave);
+    private void SetTimeoutSlavesOffline(IPersistenceManager pm) {
+      var slaveDao = pm.SlaveDao;
+      var slaves = slaveDao.GetOnlineSlaves();
+      foreach (var slave in slaves) {
+        if (!slave.LastHeartbeat.HasValue ||
+            (DateTime.Now - slave.LastHeartbeat.Value) >
+            Properties.Settings.Default.SlaveHeartbeatTimeout) {
+          slave.SlaveState = SlaveState.Offline;
         }
       }
@@ -98,8 +65,18 @@
     /// Looks for parent tasks which have FinishWhenChildJobsFinished and set their state to finished
     /// </summary>
-    private void FinishParentTasks() {
-      var parentTasksToFinish = dao.GetParentTasks(dao.GetResources(x => true).Select(x => x.Id), 0, true);
+    private void FinishParentTasks(IPersistenceManager pm) {
+      var resourceDao = pm.ResourceDao;
+      var taskDao = pm.TaskDao;
+      var resourceIds = resourceDao.GetAll().Select(x => x.ResourceId).ToList();
+      var parentTasksToFinish = taskDao.GetParentTasks(resourceIds, 0, true);
       foreach (var task in parentTasksToFinish) {
-        dao.UpdateTaskState(task.Id, TaskState.Finished, null, null, string.Empty);
+        task.State = TaskState.Finished;
+        task.StateLogs.Add(new StateLog {
+          State = task.State,
+          SlaveId = null,
+          UserId = null,
+          Exception = string.Empty,
+          DateTime = DateTime.Now
+        });
       }
     }
@@ -108,11 +85,18 @@
     /// Looks for task which have not sent heartbeats for some time and reschedules them for calculation
     /// </summary>
-    private void SetTimeoutTasksWaiting() {
-      var tasks = dao.GetTasks(x => (x.State == TaskState.Calculating && (DateTime.Now - x.LastHeartbeat) > HeuristicLab.Services.Hive.Properties.Settings.Default.CalculatingJobHeartbeatTimeout)
-                               || (x.State == TaskState.Transferring && (DateTime.Now - x.LastHeartbeat) > HeuristicLab.Services.Hive.Properties.Settings.Default.TransferringJobHeartbeatTimeout));
-      foreach (var j in tasks) {
-        DT.Task task = dao.UpdateTaskState(j.Id, TaskState.Waiting, null, null, "Slave timed out.");
+    private void SetTimeoutTasksWaiting(IPersistenceManager pm) {
+      var taskDao = pm.TaskDao;
+      var tasks = taskDao.GetAll().Where(x => (x.State == TaskState.Calculating && (DateTime.Now - x.LastHeartbeat) > Properties.Settings.Default.CalculatingJobHeartbeatTimeout)
+                                           || (x.State == TaskState.Transferring && (DateTime.Now - x.LastHeartbeat) > Properties.Settings.Default.TransferringJobHeartbeatTimeout));
+      foreach (var task in tasks) {
+        task.State = TaskState.Waiting;
+        task.StateLogs.Add(new StateLog {
+          State = task.State,
+          SlaveId = null,
+          UserId = null,
+          Exception = SlaveTimeout,
+          DateTime = DateTime.Now
+        });
         task.Command = null;
-        dao.UpdateTask(task);
       }
     }
@@ -121,15 +105,17 @@
     /// Searches for slaves that are disposable and deletes them if they were offline for too long
     /// </summary>
-    private void DeleteObsoleteSlaves() {
-      var slaves = dao.GetSlaves(x => x.IsDisposable.GetValueOrDefault() &&
-                                      x.SlaveState == SlaveState.Offline &&
-                                      (DateTime.Now - x.LastHeartbeat) > Hive.Properties.Settings.Default.SweepInterval)
-                                .Select(x => x.Id)
-                                .ToArray();
-
-      foreach (Guid slaveId in slaves) {
-        var downtimesAvailable = dao.GetDowntimes(x => x.ResourceId == slaveId).Any();
+    private void DeleteObsoleteSlaves(IPersistenceManager pm) {
+      var slaveDao = pm.SlaveDao;
+      var downtimeDao = pm.DowntimeDao;
+      var slaveIds = slaveDao.GetAll()
+        .Where(x => x.IsDisposable.GetValueOrDefault()
+                 && x.SlaveState == SlaveState.Offline
+                 && (DateTime.Now - x.LastHeartbeat) > Properties.Settings.Default.SweepInterval)
+        .Select(x => x.ResourceId)
+        .ToList();
+      foreach (var id in slaveIds) {
+        bool downtimesAvailable = downtimeDao.GetByResourceId(id).Any();
         if (!downtimesAvailable) {
-          dao.DeleteSlave(slaveId);
+          slaveDao.Delete(id);
         }
       }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs	(revision 12878)
@@ -25,18 +25,18 @@
 using System.Threading;
 using HeuristicLab.Services.Hive.DataAccess;
-using Heartbeat = HeuristicLab.Services.Hive.DataTransfer.Heartbeat;
-
-namespace HeuristicLab.Services.Hive {
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using HeuristicLab.Services.Hive.DataTransfer;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+
+namespace HeuristicLab.Services.Hive.Manager {
   public class HeartbeatManager {
     private const string MutexName = "HiveTaskSchedulingMutex";
 
-    private IOptimizedHiveDao dao {
-      get { return ServiceLocator.Instance.OptimizedHiveDao; }
-    }
-    private ITaskScheduler taskScheduler {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private ITaskScheduler TaskScheduler {
       get { return ServiceLocator.Instance.TaskScheduler; }
-    }
-    private DataAccess.ITransactionManager trans {
-      get { return ServiceLocator.Instance.TransactionManager; }
     }
 
@@ -47,9 +47,8 @@
     public List<MessageContainer> ProcessHeartbeat(Heartbeat heartbeat) {
       List<MessageContainer> actions = new List<MessageContainer>();
-
-      Slave slave = null;
-      trans.UseTransaction(() => {
-        slave = dao.GetSlaveById(heartbeat.SlaveId);
-      });
+      var pm = PersistenceManager;
+      var slaveDao = pm.SlaveDao;
+      var taskDao = pm.TaskDao;
+      var slave = pm.UseTransaction(() => slaveDao.GetById(heartbeat.SlaveId));
       if (slave == null) {
         actions.Add(new MessageContainer(MessageContainer.MessageType.SayHello));
@@ -58,22 +57,22 @@
           actions.Add(new MessageContainer(MessageContainer.MessageType.NewHBInterval));
         }
-        if (dao.SlaveHasToShutdownComputer(slave.ResourceId)) {
+        if (slaveDao.SlaveHasToShutdownComputer(slave.ResourceId)) {
           actions.Add(new MessageContainer(MessageContainer.MessageType.ShutdownComputer));
         }
-
-        // update slave data
+        // update slave data  
         slave.FreeCores = heartbeat.FreeCores;
         slave.FreeMemory = heartbeat.FreeMemory;
         slave.CpuUtilization = heartbeat.CpuUtilization;
-        slave.IsAllowedToCalculate = dao.SlaveIsAllowedToCalculate(slave.ResourceId);
-        slave.SlaveState = (heartbeat.JobProgress != null && heartbeat.JobProgress.Count > 0) ? SlaveState.Calculating : SlaveState.Idle;
+        slave.SlaveState = (heartbeat.JobProgress != null && heartbeat.JobProgress.Count > 0)
+          ? DA.SlaveState.Calculating
+          : DA.SlaveState.Idle;
         slave.LastHeartbeat = DateTime.Now;
-
-        trans.UseTransaction(() => {
-          dao.UpdateSlave(slave);
+        pm.UseTransaction(() => {
+          slave.IsAllowedToCalculate = slaveDao.SlaveIsAllowedToCalculate(slave.ResourceId);
+          pm.SubmitChanges();
         });
 
         // update task data
-        actions.AddRange(UpdateTasks(heartbeat, slave.IsAllowedToCalculate));
+        actions.AddRange(UpdateTasks(pm, heartbeat, slave.IsAllowedToCalculate));
 
         // assign new task
@@ -82,18 +81,22 @@
           var mutex = new Mutex(false, MutexName);
           try {
-
             mutexAquired = mutex.WaitOne(Properties.Settings.Default.SchedulingPatience);
-            if (!mutexAquired)
+            if (mutexAquired) {
+              var waitingTasks = pm.UseTransaction(() => taskDao.GetWaitingTasks(slave)
+                  .Select(x => new TaskInfoForScheduler {
+                    TaskId = x.TaskId,
+                    JobId = x.JobId,
+                    Priority = x.Priority
+                  })
+                  .ToList()
+              );
+              var availableTasks = TaskScheduler.Schedule(waitingTasks).ToArray();
+              if (availableTasks.Any()) {
+                var task = availableTasks.First();
+                AssignTask(pm, slave, task.TaskId);
+                actions.Add(new MessageContainer(MessageContainer.MessageType.CalculateTask, task.TaskId));
+              }
+            } else {
               LogFactory.GetLogger(this.GetType().Namespace).Log("HeartbeatManager: The mutex used for scheduling could not be aquired.");
-            else {
-              trans.UseTransaction(() => {
-                IEnumerable<TaskInfoForScheduler> availableTasks = null;
-                availableTasks = taskScheduler.Schedule(dao.GetWaitingTasks(slave).ToArray());
-                if (availableTasks.Any()) {
-                  var task = availableTasks.First();
-                  AssignTask(slave, task.TaskId);
-                  actions.Add(new MessageContainer(MessageContainer.MessageType.CalculateTask, task.TaskId));
-                }
-              });
             }
           }
@@ -102,5 +105,5 @@
           }
           catch (Exception ex) {
-            LogFactory.GetLogger(this.GetType().Namespace).Log("HeartbeatManager threw an exception in ProcessHeartbeat: " + ex.ToString());
+            LogFactory.GetLogger(this.GetType().Namespace).Log(string.Format("HeartbeatManager threw an exception in ProcessHeartbeat: {0}", ex));
           }
           finally {
@@ -112,10 +115,23 @@
     }
 
-    private void AssignTask(Slave slave, Guid taskId) {
-      var task = dao.UpdateTaskState(taskId, TaskState.Transferring, slave.ResourceId, null, null);
-
-      // from now on the task has some time to send the next heartbeat (ApplicationConstants.TransferringJobHeartbeatTimeout)
-      task.LastHeartbeat = DateTime.Now;
-      dao.UpdateTask(task);
+    private void AssignTask(IPersistenceManager pm, DA.Slave slave, Guid taskId) {
+      const DA.TaskState transferring = DA.TaskState.Transferring;
+      DateTime now = DateTime.Now;
+      var taskDao = pm.TaskDao;
+      var stateLogDao = pm.StateLogDao;
+      pm.UseTransaction(() => {
+        var task = taskDao.GetById(taskId);
+        stateLogDao.Save(new DA.StateLog {
+          State = transferring,
+          DateTime = now,
+          TaskId = taskId,
+          SlaveId = slave.ResourceId,
+          UserId = null,
+          Exception = null
+        });
+        task.State = transferring;
+        task.LastHeartbeat = now;
+        pm.SubmitChanges();
+      });
     }
 
@@ -124,52 +140,59 @@
     /// Checks if all the task sent by heartbeat are supposed to be calculated by this slave
     /// </summary>
-    private IEnumerable<MessageContainer> UpdateTasks(Heartbeat heartbeat, bool IsAllowedToCalculate) {
-      List<MessageContainer> actions = new List<MessageContainer>();
-
-      if (heartbeat.JobProgress == null)
+    private IEnumerable<MessageContainer> UpdateTasks(IPersistenceManager pm, Heartbeat heartbeat, bool isAllowedToCalculate) {
+      var taskDao = pm.TaskDao;
+      var assignedResourceDao = pm.AssignedResourceDao;
+      var actions = new List<MessageContainer>();
+      if (heartbeat.JobProgress == null || !heartbeat.JobProgress.Any())
         return actions;
 
-      if (!IsAllowedToCalculate && heartbeat.JobProgress.Count != 0) {
+      if (!isAllowedToCalculate && heartbeat.JobProgress.Count != 0) {
         actions.Add(new MessageContainer(MessageContainer.MessageType.PauseAll));
       } else {
+        // select all tasks and statelogs with one query
+        var taskIds = heartbeat.JobProgress.Select(x => x.Key).ToList();
+        var taskInfos = pm.UseTransaction(() =>
+          (from task in taskDao.GetAll()
+           where taskIds.Contains(task.TaskId)
+           let lastStateLog = task.StateLogs.OrderByDescending(x => x.DateTime).FirstOrDefault()
+           select new {
+             TaskId = task.TaskId,
+             Command = task.Command,
+             SlaveId = lastStateLog != null ? lastStateLog.SlaveId : default(Guid)
+           }).ToList()
+        );
+
         // process the jobProgresses
         foreach (var jobProgress in heartbeat.JobProgress) {
-          Tuple<Task, Guid?> taskWithLastStateLogSlaveId = null;
-          trans.UseTransaction(() => {
-            taskWithLastStateLogSlaveId = dao.GetTaskByIdAndLastStateLogSlaveId(jobProgress.Key);
-          });
-          var curTask = taskWithLastStateLogSlaveId != null ? taskWithLastStateLogSlaveId.Item1 : null;
+          var progress = jobProgress;
+          var curTask = taskInfos.SingleOrDefault(x => x.TaskId == progress.Key);
           if (curTask == null) {
-            // task does not exist in db
-            actions.Add(new MessageContainer(MessageContainer.MessageType.AbortTask, jobProgress.Key));
+            actions.Add(new MessageContainer(MessageContainer.MessageType.AbortTask, progress.Key));
             LogFactory.GetLogger(this.GetType().Namespace).Log("Task on slave " + heartbeat.SlaveId + " does not exist in DB: " + jobProgress.Key);
           } else {
-            var slaveId = taskWithLastStateLogSlaveId.Item2;
+            var slaveId = curTask.SlaveId;
             if (slaveId == Guid.Empty || slaveId != heartbeat.SlaveId) {
               // assigned slave does not match heartbeat
               actions.Add(new MessageContainer(MessageContainer.MessageType.AbortTask, curTask.TaskId));
-              LogFactory.GetLogger(this.GetType().Namespace).Log("The slave " + heartbeat.SlaveId + " is not supposed to calculate task: " + curTask);
-            } else if (!dao.TaskIsAllowedToBeCalculatedBySlave(curTask.TaskId, heartbeat.SlaveId)) {
+              LogFactory.GetLogger(this.GetType().Namespace).Log("The slave " + heartbeat.SlaveId + " is not supposed to calculate task: " + curTask.TaskId);
+            } else if (!assignedResourceDao.TaskIsAllowedToBeCalculatedBySlave(curTask.TaskId, heartbeat.SlaveId)) {
               // assigned resources ids of task do not match with slaveId (and parent resourceGroupIds); this might happen when slave is moved to different group
               actions.Add(new MessageContainer(MessageContainer.MessageType.PauseTask, curTask.TaskId));
             } else {
-              // save task execution time
-              curTask.ExecutionTimeMs = jobProgress.Value.TotalMilliseconds;
-              curTask.LastHeartbeat = DateTime.Now;
-
+              // update task execution time
+              pm.UseTransaction(() => {
+                taskDao.UpdateExecutionTime(curTask.TaskId, progress.Value.TotalMilliseconds);
+              });
               switch (curTask.Command) {
-                case Command.Stop:
+                case DA.Command.Stop:
                   actions.Add(new MessageContainer(MessageContainer.MessageType.StopTask, curTask.TaskId));
                   break;
-                case Command.Pause:
+                case DA.Command.Pause:
                   actions.Add(new MessageContainer(MessageContainer.MessageType.PauseTask, curTask.TaskId));
                   break;
-                case Command.Abort:
+                case DA.Command.Abort:
                   actions.Add(new MessageContainer(MessageContainer.MessageType.AbortTask, curTask.TaskId));
                   break;
               }
-              trans.UseTransaction(() => {
-                dao.UpdateTask(curTask);
-              });
             }
           }
Index: unk/sources/HeuristicLab.Services.Hive/3.3/OptimizedHiveDao.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/OptimizedHiveDao.cs	(revision 12877)
+++ 	(revision )
@@ -1,298 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Data.Linq;
-using System.Linq;
-using DT = HeuristicLab.Services.Hive.DataTransfer;
-
-namespace HeuristicLab.Services.Hive.DataAccess {
-  public class OptimizedHiveDao : IOptimizedHiveDao {
-    private HiveDataContext Db { get; set; }
-
-    public OptimizedHiveDao(HiveDataContext db) {
-      Db = db;
-    }
-
-    #region Task Methods
-    public Task GetTaskById(Guid taskId) {
-      return GetTaskByIdQuery(Db, taskId).SingleOrDefault();
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<Task>> GetTaskByIdQuery = CompiledQuery.Compile((HiveDataContext db, Guid taskId) =>
-      from t in db.Tasks
-      where t.TaskId == taskId
-      select t
-    );
-
-    public Task GetTaskByDto(DT.Task taskDto) {
-      var task = GetTaskById(taskDto.Id);
-      DT.Convert.ToEntity(taskDto, task);
-      return task;
-    }
-
-    public Tuple<Task, Guid?> GetTaskByIdAndLastStateLogSlaveId(Guid taskId) {
-      return GetTaskByIdAndLastStateLogSlaveIdQuery(Db, taskId).SingleOrDefault();
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<Tuple<Task, Guid?>>> GetTaskByIdAndLastStateLogSlaveIdQuery = CompiledQuery.Compile((HiveDataContext db, Guid taskId) =>
-      from t in db.Tasks
-      let lastStateLog = t.StateLogs.OrderByDescending(sl => sl.DateTime).FirstOrDefault()
-      where t.TaskId == taskId
-      select new Tuple<Task, Guid?>(t, lastStateLog != null ? lastStateLog.SlaveId : null)
-    );
-
-    private const string GetWaitingTasksQueryString = @"
-      WITH pr AS (
-        SELECT ResourceId, ParentResourceId
-        FROM [Resource]
-        WHERE ResourceId = {0}
-        UNION ALL
-        SELECT r.ResourceId, r.ParentResourceId
-        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
-      )
-      SELECT DISTINCT t.TaskId, t.JobId, t.Priority
-      FROM pr JOIN AssignedResources ar ON ar.ResourceId = pr.ResourceId
-          JOIN Task t ON t.TaskId = ar.TaskId
-      WHERE NOT (t.IsParentTask = 1 AND t.FinishWhenChildJobsFinished = 1)
-          AND t.TaskState = {1}
-          AND t.CoresNeeded <= {2}
-          AND t.MemoryNeeded <= {3}
-    ";
-
-    public IEnumerable<TaskInfoForScheduler> GetWaitingTasks(Slave slave) {
-      //Originally we checked here if there are parent tasks which should be calculated (with GetParentTasks(resourceIds, count, false);).
-      //Because there is at the moment no case where this makes sense (there don't exist parent tasks which need to be calculated), 
-      //we skip this step because it's wasted runtime
-      return Db.ExecuteQuery<TaskInfoForScheduler>(GetWaitingTasksQueryString, slave.ResourceId, Enum.GetName(typeof(TaskState), TaskState.Waiting), slave.FreeCores, slave.FreeMemory);
-    }
-
-    public IQueryable<DT.LightweightTask> GetLightweightTasks(Guid jobId) {
-      return GetLightweightTasksQuery(Db, jobId);
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<DT.LightweightTask>> GetLightweightTasksQuery = CompiledQuery.Compile((HiveDataContext db, Guid jobId) =>
-        from task in db.Tasks
-        where task.JobId == jobId
-        select new DT.LightweightTask {
-          Id = task.TaskId,
-          ExecutionTime = TimeSpan.FromMilliseconds(task.ExecutionTimeMs),
-          ParentTaskId = task.ParentTaskId,
-          StateLog = task.StateLogs.OrderBy(sl => sl.DateTime).Select(sl => ConvertStateLog(sl)).ToList(),
-          State = ConvertTaskState(task.State),
-          Command = ConvertCommand(task.Command),
-          LastTaskDataUpdate = task.JobData.LastUpdate
-        }
-    );
-
-    private static readonly Func<StateLog, DT.StateLog> ConvertStateLog = sl => DT.Convert.ToDto(sl);
-    private static readonly Func<TaskState, DT.TaskState> ConvertTaskState = ts => DT.Convert.ToDto(ts);
-    private static readonly Func<Command?, DT.Command?> ConvertCommand = c => DT.Convert.ToDto(c);
-
-    public void UpdateTask(Task task) {
-      Db.SubmitChanges();
-    }
-
-    public Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception) {
-      Db.StateLogs.InsertOnSubmit(new StateLog {
-        TaskId = taskId,
-        State = taskState,
-        SlaveId = slaveId,
-        UserId = userId,
-        Exception = exception,
-        DateTime = DateTime.Now
-      });
-
-      var task = GetTaskById(taskId);
-      task.State = taskState;
-
-      Db.SubmitChanges();
-
-      return task;
-    }
-
-    public Guid AddTask(Task task) {
-      Db.Tasks.InsertOnSubmit(task);
-      Db.SubmitChanges();
-      return task.TaskId;
-    }
-
-    public void AssignJobToResource(Guid taskId, IEnumerable<Guid> resourceIds) {
-      Db.AssignedResources.InsertAllOnSubmit(resourceIds.Select(resourceId => new AssignedResource { TaskId = taskId, ResourceId = resourceId }));
-      Db.SubmitChanges();
-    }
-
-    private const string TaskIsAllowedToBeCalculatedBySlaveQueryString = @"
-      WITH pr AS (
-        SELECT ResourceId, ParentResourceId
-        FROM [Resource]
-        WHERE ResourceId = {0}
-        UNION ALL
-        SELECT r.ResourceId, r.ParentResourceId
-        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
-      )
-      SELECT COUNT(ar.TaskId)
-      FROM pr JOIN AssignedResources ar ON pr.ResourceId = ar.ResourceId
-      WHERE ar.TaskId = {1}
-    ";
-
-    public bool TaskIsAllowedToBeCalculatedBySlave(Guid taskId, Guid slaveId) {
-      return Db.ExecuteQuery<int>(TaskIsAllowedToBeCalculatedBySlaveQueryString, slaveId, taskId).First() > 0;
-    }
-    #endregion
-
-    #region TaskData Methods
-    public TaskData GetTaskDataById(Guid id) {
-      return GetTaskDataByIdQuery(Db, id).SingleOrDefault();
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<TaskData>> GetTaskDataByIdQuery = CompiledQuery.Compile((HiveDataContext db, Guid id) =>
-      from t in db.TaskDatas
-      where t.TaskId == id
-      select t
-    );
-
-    public TaskData GetTaskDataByDto(DT.TaskData dto) {
-      var taskData = GetTaskDataById(dto.TaskId);
-      DT.Convert.ToEntity(dto, taskData);
-      return taskData;
-    }
-
-    public void UpdateTaskData(TaskData taskData) {
-      Db.SubmitChanges();
-    }
-    #endregion
-
-    #region Plugin Methods
-    public Plugin GetPluginById(Guid pluginId) {
-      return GetPluginByIdQuery(Db, pluginId).SingleOrDefault();
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<Plugin>> GetPluginByIdQuery = CompiledQuery.Compile((HiveDataContext db, Guid pluginId) =>
-      from p in db.Plugins
-      where p.PluginId == pluginId
-      select p
-    );
-    #endregion
-
-    #region Slave Methods
-    public Slave GetSlaveById(Guid id) {
-      return GetSlaveByIdQuery(Db, id).SingleOrDefault();
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<Slave>> GetSlaveByIdQuery = CompiledQuery.Compile((HiveDataContext db, Guid slaveId) =>
-      from s in db.Resources.OfType<Slave>()
-      where s.ResourceId == slaveId
-      select s
-    );
-
-    public void UpdateSlave(Slave slave) {
-      Db.SubmitChanges();
-    }
-
-    private const string DowntimeQueryString = @"
-      WITH pr AS (
-        SELECT ResourceId, ParentResourceId
-        FROM [Resource]
-        WHERE ResourceId = {0}
-        UNION ALL
-        SELECT r.ResourceId, r.ParentResourceId
-        FROM [Resource] r JOIN pr ON r.ResourceId = pr.ParentResourceId
-      )
-      SELECT COUNT(dt.DowntimeId)
-      FROM pr JOIN [Downtime] dt ON pr.ResourceId = dt.ResourceId
-      WHERE {1} BETWEEN dt.StartDate AND dt.EndDate
-        AND dt.DowntimeType = {2}
-      ";
-
-    public bool SlaveHasToShutdownComputer(Guid slaveId) {
-      return Db.ExecuteQuery<int>(DowntimeQueryString, slaveId, DateTime.Now, DowntimeType.Shutdown.ToString()).FirstOrDefault() > 0;
-    }
-
-    public bool SlaveIsAllowedToCalculate(Guid slaveId) {
-      return Db.ExecuteQuery<int>(DowntimeQueryString, slaveId, DateTime.Now, DowntimeType.Offline.ToString()).FirstOrDefault() == 0;
-    }
-    #endregion
-
-    #region Resource Methods
-    public IEnumerable<Guid> GetAssignedResourceIds(Guid taskId) {
-      return GetAssignedResourceIdsQuery(Db, taskId);
-    }
-
-    private static readonly Func<HiveDataContext, Guid, IQueryable<Guid>> GetAssignedResourceIdsQuery = CompiledQuery.Compile((HiveDataContext db, Guid taskId) =>
-      from ar in db.AssignedResources
-      where ar.TaskId == taskId
-      select ar.ResourceId
-    );
-    #endregion
-
-
-    #region Website Methods
-    private const string GetAllResourceIdsString = @"SELECT ResourceId FROM [Resource]";
-    public IEnumerable<Guid> GetAllResourceIds() {
-      return Db.ExecuteQuery<Guid>(GetAllResourceIdsString);
-    }
-
-    private const string GetNumberOfWaitingTasksString = @"SELECT COUNT(TaskId) 
-                                                           FROM [Task] 
-                                                           WHERE TaskState LIKE 'Waiting'";
-    public int GetNumberOfWaitingTasks() {
-      return Db.ExecuteQuery<int>(GetNumberOfWaitingTasksString).Single();
-    }
-
-    private class UserTasks {
-      public Guid OwnerUserId;
-      public int Count;
-    }
-
-    private const string GetCalculatingTasksByUserString = @"SELECT Job.OwnerUserId, COUNT(Task.TaskId) as Count 
-                                                             FROM Task, Job 
-                                                             WHERE TaskState LIKE 'Calculating' AND Task.JobId = Job.JobId
-                                                             GROUP BY Job.OwnerUserId";
-
-    public Dictionary<Guid, int> GetCalculatingTasksByUser() {
-      var result = Db.ExecuteQuery<UserTasks>(GetCalculatingTasksByUserString);
-      Dictionary<Guid, int> lst = new Dictionary<Guid, int>();
-
-      foreach (var userTask in result) {
-        lst.Add(userTask.OwnerUserId, userTask.Count);
-      }
-      return lst;
-    }
-
-    private const string GetWaitingTasksByUserString = @"SELECT Job.OwnerUserId, COUNT(Task.TaskId) as Count 
-                                                         FROM Task, Job 
-                                                         WHERE TaskState LIKE 'Waiting' AND Task.JobId = Job.JobId
-                                                         GROUP BY Job.OwnerUserId";
-
-    public Dictionary<Guid, int> GetWaitingTasksByUser() {
-      var result = Db.ExecuteQuery<UserTasks>(GetWaitingTasksByUserString);
-      Dictionary<Guid, int> lst = new Dictionary<Guid, int>();
-
-      foreach (var userTask in result) {
-        lst.Add(userTask.OwnerUserId, userTask.Count);
-      }
-      return lst;
-    }
-    #endregion
-  }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/PerformanceLogger.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/PerformanceLogger.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/PerformanceLogger.cs	(revision 12878)
@@ -0,0 +1,47 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Diagnostics;
+using HeuristicLab.Services.Hive.DataAccess;
+
+namespace HeuristicLab.Services.Hive {
+  public class PerformanceLogger : IDisposable {
+
+    private readonly Stopwatch stopwatch;
+    private string name;
+
+    public PerformanceLogger(string name) {
+      this.name = name;
+      if (Properties.Settings.Default.ProfileServicePerformance) {
+        stopwatch = new Stopwatch();
+        stopwatch.Start();
+      }
+    }
+    public void Dispose() {
+      if (Properties.Settings.Default.ProfileServicePerformance) {
+        stopwatch.Stop();
+        LogFactory.GetLogger(this.GetType().Namespace)
+          .Log(string.Format("{0} took {1}ms", name, stopwatch.ElapsedMilliseconds));
+      }
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs	(revision 12878)
@@ -1,9 +1,9 @@
 ﻿//------------------------------------------------------------------------------
 // <auto-generated>
-//     This code was generated by a tool.
-//     Runtime Version:4.0.30319.34014
+//     Dieser Code wurde von einem Tool generiert.
+//     Laufzeitversion:4.0.30319.42000
 //
-//     Changes to this file may cause incorrect behavior and will be lost if
-//     the code is regenerated.
+//     Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
+//     der Code erneut generiert wird.
 // </auto-generated>
 //------------------------------------------------------------------------------
@@ -13,5 +13,5 @@
     
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
     public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
         
@@ -86,4 +86,22 @@
             }
         }
+        
+        [global::System.Configuration.ApplicationScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool ProfileServicePerformance {
+            get {
+                return ((bool)(this["ProfileServicePerformance"]));
+            }
+        }
+        
+        [global::System.Configuration.ApplicationScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("00:03:00")]
+        public global::System.TimeSpan GenerateStatisticsInterval {
+            get {
+                return ((global::System.TimeSpan)(this["GenerateStatisticsInterval"]));
+            }
+        }
     }
 }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings	(revision 12878)
@@ -24,4 +24,10 @@
       <Value Profile="(Default)">00:00:20</Value>
     </Setting>
+    <Setting Name="ProfileServicePerformance" Type="System.Boolean" Scope="Application">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="GenerateStatisticsInterval" Type="System.TimeSpan" Scope="Application">
+      <Value Profile="(Default)">00:03:00</Value>
+    </Setting>
   </Settings>
 </SettingsFile>
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs	(revision 12878)
@@ -23,10 +23,11 @@
 using System.Collections.Generic;
 using System.Linq;
-using HeuristicLab.Services.Hive.DataTransfer;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
 
 namespace HeuristicLab.Services.Hive {
   public class RoundRobinTaskScheduler : ITaskScheduler {
-    private IHiveDao dao {
-      get { return ServiceLocator.Instance.HiveDao; }
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
     }
 
@@ -34,10 +35,24 @@
       if (!tasks.Any()) return Enumerable.Empty<TaskInfoForScheduler>();
 
-      var userPriorities = dao.GetUserPriorities(x => true).OrderBy(x => x.DateEnqueued).ToArray();
+      var pm = PersistenceManager;
+      var userPriorityDao = pm.UserPriorityDao;
+      var jobDao = pm.JobDao;
 
-      var jobs = new List<JobInfoForScheduler>();
-      foreach (var userPriority in userPriorities) {
-        jobs.AddRange(dao.GetJobInfoForScheduler(x => x.OwnerUserId == userPriority.Id));
-      }
+      var userPriorities = pm.UseTransaction(() => userPriorityDao.GetAll()
+        .OrderBy(x => x.DateEnqueued)
+        .ToArray()
+      );
+
+      var userIds = userPriorities.Select(x => x.UserId).ToList();
+      var jobs = pm.UseTransaction(() => {
+        return jobDao.GetAll()
+          .Where(x => userIds.Contains(x.OwnerUserId))
+          .Select(x => new {
+            Id = x.JobId,
+            DateCreated = x.DateCreated,
+            OwnerUserId = x.OwnerUserId
+          })
+          .ToList();
+      });
 
       var taskJobRelations = tasks.Join(jobs,
@@ -55,8 +70,8 @@
       for (int i = 0; i < count; i++) {
         var defaultEntry = taskJobRelations.First(); // search first task which is not included yet
-        var priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].Id).ToArray(); // search for tasks with desired user priority
+        var priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].UserId).ToArray(); // search for tasks with desired user priority
         while (!priorityEntries.Any() && priorityIndex < userPriorities.Length - 1) {
           priorityIndex++;
-          priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].Id).ToArray();
+          priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].UserId).ToArray();
         }
         if (priorityEntries.Any()) { // tasks with desired user priority found
@@ -65,5 +80,5 @@
             taskJobRelations.Remove(priorityEntry);
             scheduledTasks.Add(priorityEntry.Task);
-            UpdateUserPriority(userPriorities[priorityIndex]);
+            UpdateUserPriority(pm, userPriorities[priorityIndex]);
             priorityIndex++;
           } else { // there are other tasks with higher priorities
@@ -75,14 +90,15 @@
           scheduledTasks.Add(defaultEntry.Task);
         }
-
         if (priorityIndex >= (userPriorities.Length - 1)) priorityIndex = 0;
       }
+      return scheduledTasks;
 
-      return scheduledTasks;
     }
 
-    private void UpdateUserPriority(UserPriority up) {
-      up.DateEnqueued = DateTime.Now;
-      dao.EnqueueUserPriority(up);
+    private void UpdateUserPriority(IPersistenceManager pm, DA.UserPriority up) {
+      pm.UseTransaction(() => {
+        up.DateEnqueued = DateTime.Now;
+        pm.SubmitChanges();
+      });
     }
   }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceContracts/IHiveService.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceContracts/IHiveService.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceContracts/IHiveService.cs	(revision 12878)
@@ -41,13 +41,4 @@
 
     [OperationContract]
-    IEnumerable<Task> GetTasks();
-
-    [OperationContract]
-    IEnumerable<LightweightTask> GetLightweightTasks(IEnumerable<Guid> taskIds);
-
-    [OperationContract]
-    IEnumerable<LightweightTask> GetLightweightChildTasks(Guid? parentTaskId, bool recursive, bool includeParent);
-
-    [OperationContract]
     IEnumerable<LightweightTask> GetLightweightJobTasks(Guid jobId);
 
@@ -65,10 +56,4 @@
 
     [OperationContract]
-    void DeleteTask(Guid taskId);
-
-    [OperationContract]
-    void DeleteChildTasks(Guid parentTaskId);
-
-    [OperationContract]
     Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception);
     #endregion
@@ -89,17 +74,7 @@
     Job GetJob(Guid id);
 
-    /// <summary>
-    /// Returns all task for the current user
-    /// </summary>
     [OperationContract]
     IEnumerable<Job> GetJobs();
 
-    /// <summary>
-    /// Returns all task in the hive (only for admins)
-    /// </summary>
-    /// <returns></returns>
-    [OperationContract]
-    IEnumerable<Job> GetAllJobs();
-
     [OperationContract]
     Guid AddJob(Job jobDto);
@@ -142,7 +117,4 @@
     [OperationContract]
     Plugin GetPlugin(Guid pluginId);
-
-    [OperationContract]
-    Plugin GetPluginByHash(byte[] hash);
 
     [OperationContract]
@@ -155,7 +127,4 @@
     [OperationContract]
     IEnumerable<PluginData> GetPluginDatas(List<Guid> pluginIds);
-
-    [OperationContract]
-    void DeletePlugin(Guid pluginId);
     #endregion
 
@@ -171,9 +140,4 @@
     #endregion
 
-    #region Resource Methods
-    [OperationContract]
-    IEnumerable<Resource> GetChildResources(Guid resourceId);
-    #endregion
-
     #region Slave Methods
     [OperationContract]
@@ -187,7 +151,4 @@
 
     [OperationContract]
-    SlaveGroup GetSlaveGroup(Guid slaveGroupId);
-
-    [OperationContract]
     IEnumerable<Slave> GetSlaves();
 
@@ -217,7 +178,4 @@
 
     [OperationContract]
-    IEnumerable<Task> GetTasksByResourceId(Guid resourceId);
-
-    [OperationContract]
     void TriggerEventManager(bool force);
 
@@ -234,5 +192,5 @@
 
     [OperationContract]
-    void UpdateDowntime(Downtime downtime);
+    void UpdateDowntime(Downtime downtimeDto);
 
     [OperationContract]
@@ -251,11 +209,4 @@
     [OperationContract]
     IEnumerable<UserPriority> GetUserPriorities();
-    #endregion
-
-    #region Statistics Methods
-    [OperationContract]
-    IEnumerable<Statistics> GetStatistics();
-    [OperationContract]
-    IEnumerable<Statistics> GetStatisticsForTimePeriod(DateTime from, DateTime to);
     #endregion
   }
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceLocator.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceLocator.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/ServiceLocator.cs	(revision 12878)
@@ -21,7 +21,9 @@
 
 using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using HeuristicLab.Services.Hive.DataAccess.Manager;
+using HeuristicLab.Services.Hive.Manager;
 
 namespace HeuristicLab.Services.Hive {
-
   public class ServiceLocator : IServiceLocator {
     private static IServiceLocator instance;
@@ -34,18 +36,10 @@
     }
 
-    private IHiveDao hiveDao;
-    public IHiveDao HiveDao {
-      get {
-        if (hiveDao == null) hiveDao = new HiveDao();
-        return hiveDao;
-      }
-    }
-
-    public IOptimizedHiveDao OptimizedHiveDao {
+    public IPersistenceManager PersistenceManager {
       get {
         var dataContext = HiveOperationContext.Current != null
                             ? HiveOperationContext.Current.DataContext
                             : new HiveDataContext(Settings.Default.HeuristicLab_Hive_LinqConnectionString);
-        return new OptimizedHiveDao(dataContext);
+        return new PersistenceManager(dataContext);
       }
     }
@@ -75,10 +69,7 @@
     }
 
-    private ITransactionManager transactionManager;
-    public ITransactionManager TransactionManager {
-      get {
-        if (transactionManager == null) transactionManager = new TransactionManager();
-        return transactionManager;
-      }
+    private IStatisticsGenerator statisticsGenerator;
+    public IStatisticsGenerator StatisticsGenerator {
+      get { return statisticsGenerator ?? (statisticsGenerator = new HiveStatisticsGenerator()); }
     }
 
Index: unk/sources/HeuristicLab.Services.Hive/3.3/Settings.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/Settings.cs	(revision 12877)
+++ 	(revision )
@@ -1,49 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-namespace HeuristicLab.Services.Hive.Properties {
-    
-    
-    // This class allows you to handle specific events on the settings class:
-    //  The SettingChanging event is raised before a setting's value is changed.
-    //  The PropertyChanged event is raised after a setting's value is changed.
-    //  The SettingsLoaded event is raised after the setting values are loaded.
-    //  The SettingsSaving event is raised before the setting values are saved.
-    public sealed partial class Settings {
-        
-        public Settings() {
-            // // To add event handlers for saving and changing settings, uncomment the lines below:
-            //
-            // this.SettingChanging += this.SettingChangingEventHandler;
-            //
-            // this.SettingsSaving += this.SettingsSavingEventHandler;
-            //
-        }
-        
-        private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
-            // Add code to handle the SettingChangingEvent event here.
-        }
-        
-        private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
-            // Add code to handle the SettingsSaving event here.
-        }
-    }
-}
Index: /trunk/sources/HeuristicLab.Services.Hive/3.3/app.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.Hive/3.3/app.config	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.Hive/3.3/app.config	(revision 12878)
@@ -29,5 +29,12 @@
                 <value>00:00:20</value>
             </setting>
+            <setting name="ProfileServicePerformance" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="GenerateStatisticsInterval" serializeAs="String">
+                <value>00:03:00</value>
+            </setting>
         </HeuristicLab.Services.Hive.Properties.Settings>
     </applicationSettings>
-<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup>
+</configuration>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/HeuristicLab.Services.WebApp.Maintenance-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/HeuristicLab.Services.WebApp.Maintenance-3.3.csproj	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/HeuristicLab.Services.WebApp.Maintenance-3.3.csproj	(revision 12878)
@@ -0,0 +1,212 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{3F22B7DA-FDDB-48F0-8BB7-0ABA5120FC87}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HeuristicLab.Services.WebApp.Maintenance</RootNamespace>
+    <AssemblyName>HeuristicLab.Services.WebApp.Maintenance-3.3</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>HeuristicLab.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Core">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Net.Http">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Data.DataSetExtensions">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Microsoft.CSharp">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Data">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="WebApi\DataTransfer\FactClientInfo.cs" />
+    <Compile Include="WebApi\DataTransfer\Job.cs" />
+    <Compile Include="WebApi\DataTransfer\JobPage.cs" />
+    <Compile Include="WebApi\DataTransfer\Plugin.cs" />
+    <Compile Include="WebApi\DataTransfer\PluginPage.cs" />
+    <Compile Include="WebApi\DataTransfer\TableInformation.cs" />
+    <Compile Include="WebApi\FactClientInfoController.cs" />
+    <Compile Include="WebApi\FactTaskController.cs" />
+    <Compile Include="WebApi\PluginController.cs" />
+    <Compile Include="WebApi\SpaceUsageController.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+    <None Include="HeuristicLab.snk" />
+    <Content Include="WebApp\factclientinfo\factclientinfoCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\facttask\facttaskCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugin\plugin.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="packages.config" />
+    <Content Include="Web.config">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="Properties\AssemblyInfo.cs.frame" />
+    <None Include="WebApp\factclientinfo\factclientinfo.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\facttask\facttask.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\spaceUsage\spaceUsage.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="maintenance.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugin\pluginCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\factclientinfoService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\facttaskService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\pluginService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\spaceUsageService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\spaceUsage\spaceUsageCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup />
+  <ItemGroup>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Hive.DataAccess\3.3\HeuristicLab.Services.Hive.DataAccess-3.3.csproj">
+      <Project>{EC2C8109-6E1E-4C88-9A2B-908CFF2EF4AC}</Project>
+      <Name>HeuristicLab.Services.Hive.DataAccess-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Hive\3.3\HeuristicLab.Services.Hive-3.3.csproj">
+      <Project>{CF9DA321-AC1B-4FD3-9EC3-67BC6B861BDE}</Project>
+      <Name>HeuristicLab.Services.Hive-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Properties/AssemblyInfo.cs.frame
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Properties/AssemblyInfo.cs.frame	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Properties/AssemblyInfo.cs.frame	(revision 12878)
@@ -0,0 +1,57 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HeuristicLab.Services.WebApp.Maintenance")]
+[assembly: AssemblyDescription("HeuristicLab WebApp Maintenance plugin")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HeuristicLab")]
+[assembly: AssemblyCopyright("(c) 2002-2015 HEAL")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("daa57a9d-2df7-4b13-80b5-2496239ba459")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.3.0.0")]
+[assembly: AssemblyFileVersion("3.3.12.$WCREV$")]
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Web.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Web.config	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/Web.config	(revision 12878)
@@ -0,0 +1,9 @@
+﻿<?xml version="1.0"?>
+<configuration>
+  <system.web>
+    <authorization>
+      <allow roles="Hive Administrator"/>
+      <deny users="*" />
+    </authorization>
+  </system.web>
+</configuration>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/FactClientInfo.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/FactClientInfo.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/FactClientInfo.cs	(revision 12878)
@@ -0,0 +1,27 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class FactClientInfo {
+    public int Rows { get; set; }
+    public int Clients { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Job.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Job.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Job.cs	(revision 12878)
@@ -0,0 +1,33 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class Job {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public string Username { get; set; }
+    public DateTime DateCreated { get; set; }
+    public int Tasks { get; set; }
+    public bool Aggregated { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/JobPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class JobPage {
+    public int TotalJobs { get; set; }
+    public IEnumerable<Job> Jobs { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Plugin.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Plugin.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/Plugin.cs	(revision 12878)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class Plugin {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public string Version { get; set; }
+    public DateTime DateCreated { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/PluginPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/PluginPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/PluginPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class PluginPage {
+    public int TotalPlugins { get; set; }
+    public IEnumerable<Plugin> Plugins { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/TableInformation.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/TableInformation.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/DataTransfer/TableInformation.cs	(revision 12878)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer {
+  public class TableInformation {
+    public string Name { get; set; }
+    public int Rows { get; set; }
+    public int Reserved { get; set; }
+    public int Data { get; set; }
+    public int IndexSize { get; set; }
+    public int Unused { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactClientInfoController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactClientInfoController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactClientInfoController.cs	(revision 12878)
@@ -0,0 +1,117 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi {
+  public class FactClientInfoController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.FactClientInfo GetFactClientInfo(DateTime start, DateTime end) {
+      var pm = PersistenceManager;
+      var factClientInfo = pm.FactClientInfoDao;
+      var query = factClientInfo.GetAll().Where(x => x.Time >= start && x.Time <= end);
+      return new DT.FactClientInfo {
+        Rows = query.Count(),
+        Clients = query.Select(x => x.ClientId).Distinct().Count()
+      };
+    }
+
+    public void Aggregate(DateTime start, DateTime end, int entries) {
+      var pm = PersistenceManager;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var dimTimeDao = pm.DimTimeDao;
+      var clientIds = pm.UseTransaction(() => factClientInfoDao.GetAll()
+        .Where(x => x.Time >= start && x.Time <= end)
+        .Select(x => x.ClientId)
+        .Distinct()
+      );
+      foreach (var id in clientIds) {
+        AggregateClient(pm, id, start, end, entries);
+      }
+      dimTimeDao.DeleteUnusedTimes();
+    }
+
+    private void AggregateClient(IPersistenceManager pm, Guid clientId, DateTime start, DateTime end, int entries) {
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var clientInfos = pm.UseTransaction(() => factClientInfoDao.GetByClientId(clientId)
+        .Where(x => x.Time >= start && x.Time <= end)
+        .OrderBy(x => x.Time)
+        .ToList()
+      );
+      var e = clientInfos.GetEnumerator();
+      if (!e.MoveNext()) return;
+      do {
+        var infosToAggregate = new List<FactClientInfo> { e.Current };
+        var prev = e.Current;
+        while (e.MoveNext() && infosToAggregate.Count() != entries) {
+          var cur = e.Current;
+          if (prev.IsAllowedToCalculate != cur.IsAllowedToCalculate || prev.SlaveState != cur.SlaveState)
+            break;
+          infosToAggregate.Add(cur);
+          prev = cur;
+        }
+        if (infosToAggregate.Count() >= 2) {
+          AggregateClientInfos(pm, infosToAggregate);
+        }
+      } while (e.Current != null);
+    }
+
+    private void AggregateClientInfos(IPersistenceManager pm, List<FactClientInfo> clientInfos) {
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var last = clientInfos.Last();
+      var infos = clientInfos.GroupBy(x => x.ClientId).Select(x => new {
+        NumUsedCores = (int)x.Average(y => y.NumUsedCores),
+        NumTotalCores = (int)x.Average(y => y.NumTotalCores),
+        UsedMemory = (int)x.Average(y => y.UsedMemory),
+        TotalMemory = (int)x.Average(y => y.TotalMemory),
+        CpuUtilization = Math.Round(x.Average(y => y.CpuUtilization), 2),
+        IdleTime = x.Sum(y => y.IdleTime),
+        OfflineTime = x.Sum(y => y.OfflineTime),
+        UnavailableTime = x.Sum(y => y.UnavailableTime)
+      }).SingleOrDefault();
+      if (infos != null) {
+        pm.UseTransaction(() => {
+          last.NumUsedCores = infos.NumUsedCores;
+          last.NumTotalCores = infos.NumTotalCores;
+          last.UsedMemory = infos.UsedMemory;
+          last.TotalMemory = infos.TotalMemory;
+          last.CpuUtilization = infos.CpuUtilization;
+          last.IdleTime = infos.IdleTime;
+          last.OfflineTime = infos.OfflineTime;
+          last.UnavailableTime = infos.UnavailableTime;
+          clientInfos.Remove(last);
+          factClientInfoDao.Delete(clientInfos);
+          pm.SubmitChanges();
+        });
+      }
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactTaskController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactTaskController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/FactTaskController.cs	(revision 12878)
@@ -0,0 +1,116 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi {
+  public class FactTaskController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.JobPage GetJobs(DateTime start, DateTime end, int page, int size) {
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var query = dimJobDao.GetAll().Where(x => x.DateCreated >= start && x.DateCreated <= end);
+        return new DT.JobPage {
+          TotalJobs = query.Count(),
+          Jobs = query.OrderBy(x => x.DateCreated)
+            .Skip((page - 1) * size)
+            .Take(size)
+            .Select(x => new DT.Job {
+              Id = x.JobId,
+              Name = x.JobName,
+              Username = x.UserName,
+              DateCreated = x.DateCreated,
+              Tasks = x.TotalTasks,
+              Aggregated = !(factTaskDao.GetAll().Count(y => y.JobId == x.JobId) > 1)
+            })
+            .ToList()
+        };
+      });
+    }
+
+    [HttpPost]
+    public void AggregateJob(Guid id) {
+      var pm = PersistenceManager;
+      AggregateJob(pm, id);
+    }
+
+    [HttpPost]
+    public void AggregateAllJobs(DateTime start, DateTime end) {
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      var jobIds = pm.UseTransaction(() => dimJobDao.GetAll()
+        .Where(x => x.DateCreated >= start && x.DateCreated <= end)
+        .Select(x => x.JobId)
+        .ToList()
+      );
+      foreach (var id in jobIds) {
+        AggregateJob(pm, id);
+      }
+    }
+
+    private void AggregateJob(IPersistenceManager pm, Guid id) {
+      var jobDao = pm.JobDao;
+      var factTaskDao = pm.FactTaskDao;
+      pm.UseTransaction(() => {
+        if (jobDao.Exists(id)) return;
+        var jobTimeData = factTaskDao.GetByJobId(id).GroupBy(x => x.JobId).Select(x => new {
+          CalculatingTime = x.Sum(y => y.CalculatingTime),
+          WaitingTime = x.Sum(y => y.WaitingTime),
+          TransferTime = x.Sum(y => y.TransferTime),
+          InitialWaitingTime = x.Sum(y => y.InitialWaitingTime),
+          StartTime = x.Min(y => y.StartTime),
+          EndTime = x.Max(y => y.EndTime)
+        }).SingleOrDefault();
+        if (jobTimeData != null) {
+          factTaskDao.DeleteByJobId(id);
+          factTaskDao.Save(new FactTask {
+            TaskId = Guid.NewGuid(),
+            JobId = id,
+            CalculatingTime = jobTimeData.CalculatingTime,
+            WaitingTime = jobTimeData.WaitingTime,
+            TransferTime = jobTimeData.TransferTime,
+            InitialWaitingTime = jobTimeData.InitialWaitingTime,
+            StartTime = jobTimeData.StartTime,
+            EndTime = jobTimeData.EndTime,
+            NumCalculationRuns = 0,
+            NumRetries = 0,
+            CoresRequired = 0,
+            MemoryRequired = 0,
+            Priority = 0,
+            TaskState = TaskState.Finished
+          });
+        }
+        pm.SubmitChanges();
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/PluginController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/PluginController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/PluginController.cs	(revision 12878)
@@ -0,0 +1,82 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi {
+  public class PluginController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.PluginPage GetUnusedPlugins(int page, int size) {
+      var pm = PersistenceManager;
+      var pluginDao = pm.PluginDao;
+      var requiredPluginDao = pm.RequiredPluginDao;
+      var taskDao = pm.TaskDao;
+      return pm.UseTransaction(() => {
+        var taskIds = taskDao.GetAll().Select(x => x.TaskId);
+        var usedPluginIds = requiredPluginDao.GetAll()
+          .Where(x => taskIds.Contains(x.TaskId))
+          .Select(x => x.PluginId)
+          .Distinct();
+        var query = pluginDao.GetAll().Where(x => !usedPluginIds.Any(y => y == x.PluginId));
+        return new DT.PluginPage {
+          TotalPlugins = query.Count(),
+          Plugins = query.OrderBy(x => x.DateCreated).ThenBy(x => x.Name)
+            .Skip((page - 1) * size)
+            .Take(size).Select(x => new DT.Plugin {
+              Id = x.PluginId,
+              Name = x.Name,
+              Version = x.Version,
+              DateCreated = x.DateCreated
+            })
+            .ToList()
+        };
+      });
+    }
+
+    [HttpPost]
+    public void DeletePlugin(Guid id) {
+      var pm = PersistenceManager;
+      var pluginDao = pm.PluginDao;
+      pm.UseTransaction(() => {
+        pluginDao.Delete(id);
+        pm.SubmitChanges();
+      });
+    }
+
+    [HttpPost]
+    public void DeleteUnusedPlugins() {
+      var pm = PersistenceManager;
+      var pluginDao = pm.PluginDao;
+      pm.UseTransaction(() => {
+        pluginDao.DeleteUnusedPlugins();
+        pm.SubmitChanges();
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/SpaceUsageController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/SpaceUsageController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApi/SpaceUsageController.cs	(revision 12878)
@@ -0,0 +1,93 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using HeuristicLab.Services.WebApp.Maintenance.WebApi.DataTransfer;
+using DA = HeuristicLab.Services.Hive.DataAccess.Data;
+
+namespace HeuristicLab.Services.WebApp.Maintenance.WebApi {
+  public class SpaceUsageController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public IEnumerable<TableInformation> GetHiveTableInformation() {
+      var tables = new List<string> {
+        "AssignedResources",
+        "Downtime",
+        "Job",
+        "JobPermission",
+        "Lifecycle",
+        "Plugin",
+        "PluginData",
+        "RequiredPlugins",
+        "Resource",
+        "ResourcePermission",
+        "StateLog",
+        "Task",
+        "TaskData",
+        "UserPriority"
+      };
+      var pm = PersistenceManager;
+      return pm.UseTransaction(() => (
+          from table in tables
+          select pm.GetTableInformation(table) into tableInformation
+          where tableInformation != null
+          select Convert(tableInformation)
+          ).ToList()
+        );
+    }
+
+    public IEnumerable<TableInformation> GetStatisticsTableInformation() {
+      var tables = new List<string> {
+        "statistics.DimClient",
+        "statistics.DimJob",
+        "statistics.DimTime",
+        "statistics.DimUser",
+        "statistics.FactClientInfo",
+        "statistics.FactTask"
+      };
+      var pm = PersistenceManager;
+      return pm.UseTransaction(() => (
+          from table in tables
+          select pm.GetTableInformation(table) into tableInformation
+          where tableInformation != null
+          select Convert(tableInformation)
+          ).ToList()
+        );
+    }
+
+    private TableInformation Convert(DA.TableInformation tableInformation) {
+      return new TableInformation {
+        Name = tableInformation.Name,
+        Rows = tableInformation.Rows,
+        Reserved = tableInformation.Reserved,
+        Data = tableInformation.Data,
+        IndexSize = tableInformation.IndexSize,
+        Unused = tableInformation.Unused
+      };
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfo.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfo.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfo.cshtml	(revision 12878)
@@ -0,0 +1,77 @@
+﻿@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/maintenance/spaceusage">Space Usage</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/plugin">Plugin</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/facttask">FactTask</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/maintenance/factclientinfo">FactClientInfo</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-header">
+    <form class="form-inline">
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">From:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">To:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="toDate" is-open="toIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openToDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <button type="button" class="btn btn-default" ng-click="getFactClientInfo()">
+                Apply
+            </button>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">FactClientInfo</h3>
+                </div>
+                <div class="panel-body">
+                    <form class="form-inline">
+                        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+                            <label>Rows:&nbsp;{{factClientInfo.Rows}}</label>
+                        </div>
+                        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+                            <label>Clients:&nbsp;{{factClientInfo.Clients}}</label>
+                        </div>
+                        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+                            <label for="entries">Entries:&nbsp;</label>
+                            <input id="entries" type="number" class="form-control"
+                                   ng-model="entries" min="2"/>
+                        </div>
+                        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+                            <button type="button" class="btn btn-default" ng-click="aggregate()">
+                                Aggregate
+                            </button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfoCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfoCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/factclientinfo/factclientinfoCtrl.js	(revision 12878)
@@ -0,0 +1,58 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    module.controller('app.maintenance.factclientinfoCtrl',
+        ['$scope', '$interval', 'app.maintenance.factclientinfoService', function ($scope, $interval, factclientinfoService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.entries = 2;
+
+            $scope.fromDate = new Date();
+            $scope.toDate = new Date();
+
+            $scope.fromIsOpen = false;
+            $scope.toIsOpen = false;
+
+            $scope.openFromDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.toIsOpen = false;
+                $scope.fromIsOpen = true;
+            };
+
+            $scope.openToDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.fromIsOpen = false;
+                $scope.toIsOpen = true;
+            };
+
+            $scope.dateOptions = {
+                formatYear: 'yy',
+                startingDay: 1
+            };
+
+            $scope.getFactClientInfo = function () {
+                factclientinfoService.getFactClientInfo({
+                    start: ConvertFromDate($scope.fromDate),
+                    end: ConvertToDate($scope.toDate)
+                },
+                function (factClientInfo) {
+                    $scope.factClientInfo = factClientInfo;
+                    $scope.aggregateStartDate = ConvertFromDate($scope.fromDate);
+                    $scope.aggregateEndDate = ConvertToDate($scope.toDate);
+                });
+            };
+
+            $scope.aggregate = function () {
+                factclientinfoService.aggregate({
+                    start: $scope.aggregateStartDate,
+                    end: $scope.aggregateEndDate,
+                    entries: $scope.entries
+                }, function () {
+                    $scope.getFactClientInfo();
+                });
+            };
+
+            $scope.getFactClientInfo(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttask.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttask.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttask.cshtml	(revision 12878)
@@ -0,0 +1,90 @@
+﻿@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/maintenance/spaceusage">Space Usage</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/plugin">Plugin</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/maintenance/facttask">FactTask</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/factclientinfo">FactClientInfo</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-header">
+    <form class="form-inline">
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">From:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">To:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="toDate" is-open="toIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openToDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <button type="button" class="btn btn-default" ng-click="getJobs()">
+                Apply
+            </button>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed" ts-wrapper>
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th ts-criteria="Name">Job Name</th>
+                            <th ts-criteria="Username">Username</th>
+                            <th ts-criteria="DateCreated">Date Created</th>
+                            <th ts-criteria="Tasks">Tasks</th>
+                            <th class="text-center">
+                                <a ng-show="jobPage.Jobs.length" class="cursor-pointer" ng-href="" ng-click="aggregateAllJobs()">Aggregate All</a>
+                            </th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="job in jobPage.Jobs" ts-repeat>
+                                <td>{{($index + 1)+((curJobsPage-1)*(jobsPageSize))}}</td>
+                                <td>{{job.Name}}</td>
+                                <td>{{job.Username}}</td>
+                                <td>{{job.DateCreated | toDate}}</td>
+                                <td>{{job.Tasks}}</td>
+                                <td class="text-center">
+                                    <a ng-hide="job.Aggregated" class="cursor-pointer" ng-href="" ng-click="aggregateJob(job.Id)">Aggregate</a>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <div class="row text-center" ng-show="jobPage.TotalJobs > jobPage.Jobs.length">
+                        <pagination max-size="10" total-items="jobPage.TotalJobs" ng-model="curJobsPage" ng-change="changeJobsPage()" items-per-page="jobsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttaskCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttaskCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/facttask/facttaskCtrl.js	(revision 12878)
@@ -0,0 +1,69 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    module.controller('app.maintenance.facttaskCtrl',
+        ['$scope', '$interval', 'app.maintenance.facttaskService', function ($scope, $interval, facttaskService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curJobsPage = 1;
+            $scope.jobsPageSize = 20;
+
+            $scope.fromDate = new Date();
+            $scope.toDate = new Date();
+
+            $scope.fromIsOpen = false;
+            $scope.toIsOpen = false;
+
+            $scope.openFromDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.toIsOpen = false;
+                $scope.fromIsOpen = true;
+            };
+
+            $scope.openToDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.fromIsOpen = false;
+                $scope.toIsOpen = true;
+            };
+
+            $scope.dateOptions = {
+                formatYear: 'yy',
+                startingDay: 1
+            };
+
+            $scope.getJobs = function () {
+                facttaskService.getJobs({
+                    start: ConvertFromDate($scope.fromDate),
+                    end: ConvertToDate($scope.toDate),
+                    page: $scope.curJobsPage,
+                    size: $scope.jobsPageSize
+                },
+                function (jobPage) {
+                    $scope.jobPage = jobPage;
+                });
+            };
+
+            $scope.aggregateJob = function (id) {
+                facttaskService.aggregateJob({ id: id }, function () {
+                    $scope.getJobs();
+                });
+            };
+
+            $scope.aggregateAllJobs = function () {
+                facttaskService.aggregateAllJobs({
+                    start: ConvertFromDate($scope.fromDate),
+                    end: ConvertToDate($scope.toDate)
+                },
+                function () {
+                    $scope.getJobs();
+                });
+            };
+
+            $scope.changeJobsPage = function () {
+                $scope.getJobs();
+            };
+
+            $scope.getJobs(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/plugin.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/plugin.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/plugin.cshtml	(revision 12878)
@@ -0,0 +1,58 @@
+﻿@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/maintenance/spaceusage">Space Usage</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/maintenance/plugin">Plugin</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/facttask">FactTask</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/factclientinfo">FactClientInfo</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Unused Plugins</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed" ts-wrapper>
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th ts-criteria="Name">Plugin Name</th>
+                                <th ts-criteria="Version">Version</th>
+                                <th ts-criteria="DateCreated">Date Created</th>
+                                <th class="text-center">
+                                    <a ng-show="pluginPage.Plugins.length" class="cursor-pointer" ng-href="" ng-click="deleteUnusedPlugins()">Delete All</a>
+                                </th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="plugin in pluginPage.Plugins" ts-repeat>
+                                <td>{{($index + 1)+((curPluginsPage-1)*(pluginsPageSize))}}</td>
+                                <td>{{plugin.Name}}</td>
+                                <td>{{plugin.Version}}</td>
+                                <td>{{plugin.DateCreated | toDate}}</td>
+                                <td class="text-center"><a class="cursor-pointer" ng-href="" ng-click="deletePlugin(plugin.Id)">Delete</a></td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <div class="row text-center" ng-show="pluginPage.TotalPlugins > pluginPage.Plugins.length">
+                        <pagination max-size="10" total-items="pluginPage.TotalPlugins" ng-model="curPluginsPage" ng-change="changePluginsPage()" items-per-page="pluginsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/pluginCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/pluginCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/plugin/pluginCtrl.js	(revision 12878)
@@ -0,0 +1,39 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    module.controller('app.maintenance.pluginCtrl',
+        ['$scope', '$interval', 'app.maintenance.pluginService', function ($scope, $interval, pluginService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curPluginsPage = 1;
+            $scope.pluginsPageSize = 20;
+
+            var getUnusedPlugins = function () {
+                pluginService.getUnusedPlugins({ page: $scope.curPluginsPage, size: $scope.pluginsPageSize }, function (pluginPage) {
+                    $scope.pluginPage = pluginPage;
+                });
+            };
+
+            $scope.deletePlugin = function(id) {
+                pluginService.deletePlugin({ id: id }, function() {
+                    getUnusedPlugins();
+                });
+            };
+
+            $scope.deleteUnusedPlugins = function() {
+                pluginService.deleteUnusedPlugins({}, function() {
+                    getUnusedPlugins();
+                });
+            };
+
+            $scope.changePluginsPage = function () {
+                getUnusedPlugins();
+            };
+
+            $scope.updateInterval = $interval(getUnusedPlugins, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            getUnusedPlugins(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/factclientinfoService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/factclientinfoService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/factclientinfoService.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    var apiUrl = 'api/Maintenance/FactClientInfo/';
+    module.factory('app.maintenance.factclientinfoService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { start: '@start', end: '@end', entries: '@entries' }, {
+                getFactClientInfo: { method: 'GET', params: { action: 'GetFactClientInfo' } },
+                aggregate: { method: 'POST', params: { action: 'Aggregate' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/facttaskService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/facttaskService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/facttaskService.js	(revision 12878)
@@ -0,0 +1,13 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    var apiUrl = 'api/Maintenance/FactTask/';
+    module.factory('app.maintenance.facttaskService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { start: '@start', end: '@end', id: '@id', page: '@page', size: '@size' }, {
+                getJobs: { method: 'GET', params: { action: 'GetJobs' } },
+                aggregateJob: { method: 'POST', params: { action: 'AggregateJob' } },
+                aggregateAllJobs: { method: 'POST', params: { action: 'AggregateAllJobs' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/pluginService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/pluginService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/pluginService.js	(revision 12878)
@@ -0,0 +1,13 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    var apiUrl = 'api/Maintenance/Plugin/';
+    module.factory('app.maintenance.pluginService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size' }, {
+                getUnusedPlugins: { method: 'GET', params: { action: 'GetUnusedPlugins' } },
+                deletePlugin: { method: 'POST', params: { action: 'DeletePlugin' } },
+                deleteUnusedPlugins: { method: 'POST', params: { action: 'DeleteUnusedPlugins' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/spaceUsageService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/spaceUsageService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/services/spaceUsageService.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    var apiUrl = 'api/Maintenance/SpaceUsage/';
+    module.factory('app.maintenance.spaceUsageService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', {}, {
+                getHiveTableInformation: { method: 'GET', params: { action: 'GetHiveTableInformation' }, isArray: true },
+                getStatisticsTableInformation: { method: 'GET', params: { action: 'GetStatisticsTableInformation' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsage.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsage.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsage.cshtml	(revision 12878)
@@ -0,0 +1,91 @@
+﻿@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li class="active">
+            <a ng-href="#/maintenance/spaceusage">Space Usage</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/plugin">Plugin</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/facttask">FactTask</a>
+        </li>
+        <li>
+            <a ng-href="#/maintenance/factclientinfo">FactClientInfo</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Statistics Tables Space Usage</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed" ts-wrapper>
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th ts-criteria="Name">Table Name</th>
+                                <th ts-criteria="Rows" class="text-right">Rows</th>
+                                <th ts-criteria="Reserved" class="text-right">Reserved</th>
+                                <th ts-criteria="Data" class="text-right">Data</th>
+                                <th ts-criteria="IndexSize" class="text-right">Index Size</th>
+                                <th ts-criteria="Unused" class="text-right">Unused</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="tableInformation in statisticsTableInformation" ts-repeat>
+                                <td>{{$index + 1}}</td>
+                                <td>{{tableInformation.Name}}</td>
+                                <td class="text-right">{{tableInformation.Rows}}</td>
+                                <td class="text-right">{{tableInformation.Reserved}} KB</td>
+                                <td class="text-right">{{tableInformation.Data}} KB</td>
+                                <td class="text-right">{{tableInformation.IndexSize}} KB</td>
+                                <td class="text-right">{{tableInformation.Unused}} KB</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Hive Tables Space Usage</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed" ts-wrapper>
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th ts-criteria="Name">Table Name</th>
+                                <th ts-criteria="Rows" class="text-right">Rows</th>
+                                <th ts-criteria="Reserved" class="text-right">Reserved</th>
+                                <th ts-criteria="Data" class="text-right">Data</th>
+                                <th ts-criteria="IndexSize" class="text-right">Index Size</th>
+                                <th ts-criteria="Unused" class="text-right">Unused</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="tableInformation in hiveTableInformation" ts-repeat>
+                                <td>{{$index + 1}}</td>
+                                <td>{{tableInformation.Name}}</td>
+                                <td class="text-right">{{tableInformation.Rows}}</td>
+                                <td class="text-right">{{tableInformation.Reserved}} KB</td>
+                                <td class="text-right">{{tableInformation.Data}} KB</td>
+                                <td class="text-right">{{tableInformation.IndexSize}} KB</td>
+                                <td class="text-right">{{tableInformation.Unused}} KB</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsageCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsageCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/WebApp/spaceUsage/spaceUsageCtrl.js	(revision 12878)
@@ -0,0 +1,24 @@
+﻿(function () {
+    var module = appMaintenancePlugin.getAngularModule();
+    module.controller('app.maintenance.spaceUsageCtrl',
+        ['$scope', '$interval', 'app.maintenance.spaceUsageService', function ($scope, $interval, spaceUsageService) {
+            $scope.interval = defaultPageUpdateInterval;
+
+            var getTableInformations = function () {
+                spaceUsageService.getHiveTableInformation({}, function (tableInformations) {
+                    $scope.hiveTableInformation = tableInformations;
+                });
+                spaceUsageService.getStatisticsTableInformation({}, function (tableInformations) {
+                    $scope.statisticsTableInformation = tableInformations;
+                });
+            };
+
+            $scope.updateInterval = $interval(getTableInformations, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            getTableInformations(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/app.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/app.config	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/app.config	(revision 12878)
@@ -0,0 +1,11 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/maintenance.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/maintenance.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/maintenance.js	(revision 12878)
@@ -0,0 +1,32 @@
+﻿var appMaintenancePlugin = app.registerPlugin('maintenance');
+(function () {
+    var plugin = appMaintenancePlugin;
+    plugin.dependencies = ['ngResource', 'ui.knob', 'ui.bootstrap', 'tableSort'];
+    plugin.files = [
+        'WebApp/services/spaceUsageService.js',
+        'WebApp/services/pluginService.js',
+        'WebApp/services/facttaskService.js',
+        'WebApp/services/factclientinfoService.js',
+        'WebApp/plugin/pluginCtrl.js',
+        'WebApp/facttask/facttaskCtrl.js',
+        'WebApp/factclientinfo/factclientinfoCtrl.js',
+        'WebApp/spaceUsage/spaceUsageCtrl.js'
+    ];
+    plugin.view = 'WebApp/spaceUsage/spaceUsage.cshtml';
+    plugin.controller = 'app.maintenance.spaceUsageCtrl';
+    plugin.routes = [
+        new Route('spaceusage', 'WebApp/spaceUsage/spaceUsage.cshtml', 'app.maintenance.spaceUsageCtrl'),
+        new Route('facttask', 'WebApp/facttask/facttask.cshtml', 'app.maintenance.facttaskCtrl'),
+        new Route('factclientinfo', 'WebApp/factclientinfo/factclientinfo.cshtml', 'app.maintenance.factclientinfoCtrl'),
+        new Route('plugin', 'WebApp/plugin/plugin.cshtml', 'app.maintenance.pluginCtrl')
+    ];
+    var menu = app.getMenu();
+    var section = menu.getSection('Administration', -1);
+    section.addEntry({
+        name: 'Maintenance',
+        route: '#/maintenance',
+        icon: 'glyphicon glyphicon-cog',
+        entries: []
+    });
+})();
+
Index: /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/packages.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/packages.config	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Maintenance/3.3/packages.config	(revision 12878)
@@ -0,0 +1,8 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+</packages>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj	(revision 12878)
@@ -0,0 +1,308 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{1BC54353-7D4C-4734-BAA9-3D11B3242B59}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HeuristicLab.Services.WebApp.Statistics</RootNamespace>
+    <AssemblyName>HeuristicLab.Services.WebApp.Statistics-3.3</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <RestorePackages>true</RestorePackages>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+  <PropertyGroup>
+    <AssemblyOriginatorKeyFile>HeuristicLab.snk</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <Private>False</Private>
+      <HintPath>..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="WebApi\ClientController.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="WebApi\DataTransfer\Client.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientDetails.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientPage.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientStatus.cs" />
+    <Compile Include="WebApi\DataTransfer\ExceptionPage.cs" />
+    <Compile Include="WebApi\DataTransfer\Group.cs" />
+    <Compile Include="WebApi\DataTransfer\GroupDetails.cs" />
+    <Compile Include="WebApi\DataTransfer\GroupPage.cs" />
+    <Compile Include="WebApi\DataTransfer\Job.cs" />
+    <Compile Include="WebApi\DataTransfer\JobDetails.cs" />
+    <Compile Include="WebApi\DataTransfer\JobPage.cs" />
+    <Compile Include="WebApi\DataTransfer\Task.cs" />
+    <Compile Include="WebApi\DataTransfer\Exception.cs" />
+    <Compile Include="WebApi\DataTransfer\TaskPage.cs" />
+    <Compile Include="WebApi\DataTransfer\TaskStateCount.cs" />
+    <Compile Include="WebApi\DataTransfer\User.cs" />
+    <Compile Include="WebApi\DataTransfer\UserDetails.cs" />
+    <Compile Include="WebApi\ExceptionController.cs" />
+    <Compile Include="WebApi\GroupController.cs" />
+    <Compile Include="WebApi\JavascriptUtils.cs" />
+    <Compile Include="WebApi\JobController.cs" />
+    <Compile Include="WebApi\TaskController.cs" />
+    <Compile Include="WebApi\UserController.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="statistics.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\clients\details\clientDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\clients\clientsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\clients\details\clientTaskDetailsDialogCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\exceptions\exceptionsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\exceptions\exceptionDetailsDialogCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\groups\details\groupDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\groups\groupsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\details\jobDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\details\jobTaskDetailsDialogCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\jobsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\clientService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\exceptionService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\groupService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\jobService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\statisticsService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\taskService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\userService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\statistics.css">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\users\details\userDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\users\usersCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="HeuristicLab.snk" />
+    <Content Include="Properties\AssemblyInfo.cs.frame" />
+    <None Include="packages.config" />
+    <Content Include="Web.config">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\clients\clients.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\jobs.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\users\users.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="WebApp\clients\details\clientDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\clients\details\clientTaskDetailsDialog.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <Content Include="WebApp\groups\groups.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="WebApp\exceptions\exceptions.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\exceptions\exceptionDetailsDialog.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\groups\details\groupDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\jobs\details\jobDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\jobs\details\jobTaskDetailsDialog.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <Content Include="WebApp\users\details\userDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup />
+  <ItemGroup>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Access\3.3\HeuristicLab.Services.Access-3.3.csproj">
+      <Project>{9FAC0B23-2730-452A-9BA0-D7CA1746C541}</Project>
+      <Name>HeuristicLab.Services.Access-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Hive.DataAccess\3.3\HeuristicLab.Services.Hive.DataAccess-3.3.csproj">
+      <Project>{ec2c8109-6e1e-4c88-9a2b-908cff2ef4ac}</Project>
+      <Name>HeuristicLab.Services.Hive.DataAccess-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+    <ProjectReference Include="..\..\HeuristicLab.Services.Hive\3.3\HeuristicLab.Services.Hive-3.3.csproj">
+      <Project>{cf9da321-ac1b-4fd3-9ec3-67bc6b861bde}</Project>
+      <Name>HeuristicLab.Services.Hive-3.3</Name>
+      <Private>False</Private>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
+  </Target>
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Properties/AssemblyInfo.cs.frame
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Properties/AssemblyInfo.cs.frame	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Properties/AssemblyInfo.cs.frame	(revision 12878)
@@ -0,0 +1,57 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("HeuristicLab.Services.WebApp.Statistics")]
+[assembly: AssemblyDescription("HeuristicLab WebApp Statistics plugin")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HeuristicLab")]
+[assembly: AssemblyCopyright("(c) 2002-2015 HEAL")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("daa57a9d-2df7-4b13-80b5-2496239ba459")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("3.3.0.0")]
+[assembly: AssemblyFileVersion("3.3.12.$WCREV$")]
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Web.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Web.config	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/Web.config	(revision 12878)
@@ -0,0 +1,9 @@
+﻿<?xml version="1.0"?>
+<configuration>
+  <system.web>
+    <authorization>
+      <allow roles="Hive User"/>
+      <deny users="*" />
+    </authorization>
+  </system.web>
+</configuration>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs	(revision 12878)
@@ -0,0 +1,295 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize]
+  public class ClientController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.ClientDetails GetClientDetails(Guid id) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var timeData = factClientInfoDao.GetByClientId(id)
+          .GroupBy(x => x.ClientId)
+          .Select(x => new {
+            TotalIdleTime = x.Sum(y => y.IdleTime),
+            TotalOfflineTime = x.Sum(y => y.OfflineTime),
+            TotalUnavailableTime = x.Sum(y => y.UnavailableTime)
+          }).FirstOrDefault();
+        var taskTimeData = factTaskDao.GetByClientId(id)
+          .GroupBy(x => x.LastClientId)
+          .Select(x => new {
+            TotalCalculatingTime = x.Sum(y => y.CalculatingTime),
+            TotalTransferringTime = x.Sum(y => y.TransferTime)
+          }).FirstOrDefault();
+        var startDate = factClientInfoDao.GetByClientId(id)
+          .Where(x => x.SlaveState == SlaveState.Offline)
+          .OrderByDescending(x => x.Time)
+          .Select(x => x.Time).FirstOrDefault();
+        long upTime = 0;
+        if (startDate == default(DateTime)) {
+          startDate = factClientInfoDao.GetByClientId(id)
+            .OrderByDescending(x => x.Time)
+            .Select(x => x.Time)
+            .FirstOrDefault();
+        }
+        if (startDate != default(DateTime)) {
+          upTime = (long)(DateTime.Now - startDate).TotalSeconds;
+        }
+        return (from client in dimClientDao.GetAll().Where(x => x.Id == id)
+                join info in factClientInfoDao.GetAll()
+                  on client.Id equals info.ClientId into clientInfoJoin
+                from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                let offline = (client.ExpirationTime != null || clientInfo.SlaveState == SlaveState.Offline)
+                select new DT.ClientDetails {
+                  Id = client.Id,
+                  Name = client.Name,
+                  TotalCores = clientInfo.NumTotalCores,
+                  UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                  TotalMemory = clientInfo.TotalMemory,
+                  UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                  CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                  State = offline ? SlaveState.Offline.ToString() : clientInfo.SlaveState.ToString(),
+                  LastUpdate = clientInfo.Time,
+                  GroupId = client.ResourceGroupId,
+                  GroupName = client.GroupName,
+                  UpTime = offline ? 0 : upTime,
+                  TotalUnavailableTime = timeData != null ? timeData.TotalUnavailableTime : 0,
+                  TotalCalculatingTime = taskTimeData != null ? taskTimeData.TotalCalculatingTime : 0,
+                  TotalIdleTime = timeData != null ? timeData.TotalIdleTime : 0,
+                  TotalOfflineTime = timeData != null ? timeData.TotalOfflineTime : 0,
+                  TotalTransferringTime = taskTimeData != null ? taskTimeData.TotalTransferringTime : 0,
+                  TasksStates = factTaskDao.GetByClientId(id)
+                                  .GroupBy(x => x.TaskState)
+                                  .Select(x => new DT.TaskStateCount {
+                                    State = x.Key.ToString(),
+                                    Count = x.Count()
+                                  }).ToList(),
+                  Users = factTaskDao.GetAll()
+                            .Where(x => x.LastClientId == id)
+                            .Select(x => new DT.User {
+                              Id = x.DimJob.UserId,
+                              Name = x.DimJob.UserName
+                            })
+                            .Distinct()
+                            .ToList()
+                })
+                .FirstOrDefault();
+      });
+    }
+
+    public DT.ClientPage GetClients(int page, int size, bool expired = false) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      return pm.UseTransaction(() => {
+        var clients = expired ? dimClientDao.GetExpiredClients() : dimClientDao.GetActiveClients();
+        var query = (from client in clients
+                     join info in factClientInfoDao.GetAll()
+                       on client.Id equals info.ClientId into clientInfoJoin
+                     from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                     let offline = (expired || clientInfo.SlaveState == SlaveState.Offline)
+                     select new DT.Client {
+                       Id = client.Id,
+                       Name = client.Name,
+                       TotalCores = clientInfo.NumTotalCores,
+                       UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                       TotalMemory = clientInfo.TotalMemory,
+                       UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                       CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                       State = offline ? SlaveState.Offline.ToString() : clientInfo.SlaveState.ToString(),
+                       GroupId = client.ResourceGroupId,
+                       GroupName = client.GroupName,
+                       IsAllowedToCalculate = clientInfo.IsAllowedToCalculate
+                     });
+        return new DT.ClientPage {
+          TotalClients = query.Count(),
+          Clients = query.Skip((page - 1) * size)
+            .Take(size)
+            .ToList()
+        };
+      });
+    }
+
+    public IEnumerable<DT.ClientStatus> GetClientHistory(Guid id, DateTime start, DateTime end) {
+      TimeSpan ts = end - start;
+      int increment = 1;
+      double totalMinutes = ts.TotalMinutes;
+      while (totalMinutes > 5761) {
+        totalMinutes -= 5761;
+        increment += 5;
+      }
+      var pm = PersistenceManager;
+      var factClientInfo = pm.FactClientInfoDao;
+      var clientInfos = factClientInfo.GetByClientId(id)
+        .Where(x => x.Time >= start && x.Time <= end)
+        .OrderBy(x => x.Time)
+        .ToList();
+      var clientStatus = new DT.ClientStatus {
+        CpuUtilization = 0,
+        TotalCores = 0,
+        UsedCores = 0,
+        TotalMemory = 0,
+        UsedMemory = 0
+      };
+      int i = 1;
+      foreach (var clientInfo in clientInfos) {
+        clientStatus.CpuUtilization += clientInfo.CpuUtilization;
+        clientStatus.TotalCores += clientInfo.NumTotalCores;
+        clientStatus.UsedCores += clientInfo.NumUsedCores;
+        clientStatus.TotalMemory += clientInfo.TotalMemory;
+        clientStatus.UsedMemory += clientInfo.UsedMemory;
+        if (i >= increment) {
+          clientStatus.Timestamp = JavascriptUtils.ToTimestamp(clientInfo.Time);
+          clientStatus.CpuUtilization /= i;
+          clientStatus.TotalCores /= i;
+          clientStatus.UsedCores /= i;
+          clientStatus.TotalMemory /= i;
+          clientStatus.UsedMemory /= i;
+          yield return clientStatus;
+          clientStatus = new DT.ClientStatus {
+            CpuUtilization = 0,
+            TotalCores = 0,
+            UsedCores = 0,
+            TotalMemory = 0,
+            UsedMemory = 0
+          };
+          i = 1;
+        } else {
+          ++i;
+        }
+      }
+    }
+
+    public DT.ClientPage GetClientsByGroupId(Guid id, int page, int size, bool expired = false) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      return pm.UseTransaction(() => {
+        var clients = expired ? dimClientDao.GetExpiredClients() : dimClientDao.GetActiveClients();
+        clients = clients.Where(x => x.ResourceGroupId == id);
+        var query = (from client in clients
+                     join info in factClientInfoDao.GetAll()
+                       on client.Id equals info.ClientId into clientInfoJoin
+                     from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                     let offline = (expired || clientInfo.SlaveState == SlaveState.Offline)
+                     select new DT.Client {
+                       Id = client.Id,
+                       Name = client.Name,
+                       TotalCores = clientInfo.NumTotalCores,
+                       UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                       TotalMemory = clientInfo.TotalMemory,
+                       UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                       CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                       State = offline ? SlaveState.Offline.ToString() : clientInfo.SlaveState.ToString(),
+                       GroupId = client.ResourceGroupId,
+                       GroupName = client.GroupName,
+                       IsAllowedToCalculate = clientInfo.IsAllowedToCalculate
+                     });
+        return new DT.ClientPage {
+          TotalClients = query.Count(),
+          Clients = query.Skip((page - 1) * size)
+            .Take(size)
+            .ToList()
+        };
+      });
+    }
+
+    public IEnumerable<DT.ClientStatus> GetClientHistoryByGroupId(Guid id, DateTime start, DateTime end) {
+      TimeSpan ts = end - start;
+      int increment = 1;
+      double totalMinutes = ts.TotalMinutes;
+      while (totalMinutes > 5761) {
+        totalMinutes -= 5761;
+        increment += 5;
+      }
+      var pm = PersistenceManager;
+      var factClientInfo = pm.FactClientInfoDao;
+      var dimClientDao = pm.DimClientDao;
+      var clientInfos = factClientInfo.GetAll()
+        .Where(x => x.Time >= start && x.Time <= end)
+        .OrderBy(x => x.Time)
+        .Join(dimClientDao.GetAll(),
+          fact => fact.ClientId,
+          client => client.Id,
+          (fact, client) => new {
+            client.ResourceGroupId,
+            fact.Time,
+            fact.CpuUtilization,
+            fact.NumTotalCores,
+            fact.NumUsedCores,
+            fact.TotalMemory,
+            fact.UsedMemory
+          })
+        .Where(x => x.ResourceGroupId == id)
+        .ToList();
+      var clientStatus = new DT.ClientStatus {
+        CpuUtilization = 0,
+        TotalCores = 0,
+        UsedCores = 0,
+        TotalMemory = 0,
+        UsedMemory = 0
+      };
+      int i = 1;
+      foreach (var clientInfo in clientInfos) {
+        clientStatus.CpuUtilization += clientInfo.CpuUtilization;
+        clientStatus.TotalCores += clientInfo.NumTotalCores;
+        clientStatus.UsedCores += clientInfo.NumUsedCores;
+        clientStatus.TotalMemory += clientInfo.TotalMemory;
+        clientStatus.UsedMemory += clientInfo.UsedMemory;
+        if (i >= increment) {
+          clientStatus.Timestamp = JavascriptUtils.ToTimestamp(clientInfo.Time);
+          clientStatus.CpuUtilization /= i;
+          clientStatus.TotalCores /= i;
+          clientStatus.UsedCores /= i;
+          clientStatus.TotalMemory /= i;
+          clientStatus.UsedMemory /= i;
+          yield return clientStatus;
+          clientStatus = new DT.ClientStatus {
+            CpuUtilization = 0,
+            TotalCores = 0,
+            UsedCores = 0,
+            TotalMemory = 0,
+            UsedMemory = 0
+          };
+          i = 1;
+        } else {
+          ++i;
+        }
+      }
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs	(revision 12878)
@@ -0,0 +1,39 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Client {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+    public string State { get; set; }
+    public DateTime LastUpdate { get; set; }
+    public Guid? GroupId { get; set; }
+    public string GroupName { get; set; }
+    public bool IsAllowedToCalculate { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs	(revision 12878)
@@ -0,0 +1,47 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+    public string State { get; set; }
+    public DateTime LastUpdate { get; set; }
+    public Guid? GroupId { get; set; }
+    public string GroupName { get; set; }
+    public long UpTime { get; set; }
+    public long TotalUnavailableTime { get; set; }
+    public long TotalCalculatingTime { get; set; }
+    public long TotalIdleTime { get; set; }
+    public long TotalOfflineTime { get; set; }
+    public long TotalTransferringTime { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+    public IEnumerable<User> Users { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientPage {
+    public int TotalClients { get; set; }
+    public IEnumerable<Client> Clients { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs	(revision 12878)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientStatus {
+    public long Timestamp { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Exception.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Exception.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Exception.cs	(revision 12878)
@@ -0,0 +1,36 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Exception {
+    public Guid TaskId { get; set; }
+    public Guid JobId { get; set; }
+    public string JobName { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
+    public Guid ClientId { get; set; }
+    public string ClientName { get; set; }
+    public DateTime Date { get; set; }
+    public string Details { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ExceptionPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ExceptionPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ExceptionPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ExceptionPage {
+    public int TotalExceptions { get; set; }
+    public IEnumerable<Exception> Exceptions { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Group.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Group.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Group.cs	(revision 12878)
@@ -0,0 +1,36 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Group {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+    public int TotalClients { get; set; }
+    public int OnlineClients { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupDetails.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupDetails.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupDetails.cs	(revision 12878)
@@ -0,0 +1,44 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class GroupDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double TotalCpuUtilization { get; set; }
+    public double ActiveCpuUtilization { get; set; }
+    public int TotalClients { get; set; }
+    public int OnlineClients { get; set; }
+    public long TotalUnavailableTime { get; set; }
+    public long TotalCalculatingTime { get; set; }
+    public long TotalIdleTime { get; set; }
+    public long TotalOfflineTime { get; set; }
+    public long TotalTransferringTime { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/GroupPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class GroupPage {
+    public int TotalGroups { get; set; }
+    public IEnumerable<Group> Groups { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs	(revision 12878)
@@ -0,0 +1,35 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Job {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
+    public DateTime DateCreated { get; set; }
+    public int TotalTasks { get; set; }
+    public int CompletedTasks { get; set; }
+    public DateTime? DateCompleted { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs	(revision 12878)
@@ -0,0 +1,44 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class JobDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
+    public DateTime DateCreated { get; set; }
+    public int TotalTasks { get; set; }
+    public int CompletedTasks { get; set; }
+    public DateTime? DateCompleted { get; set; }
+    public long AvgTransferringTime { get; set; }
+    public long MinCalculatingTime { get; set; }
+    public long MaxCalculatingTime { get; set; }
+    public long AvgCalculatingTime { get; set; }
+    public long TotalCalculatingTime { get; set; }
+    public long TotalWaitingTime { get; set; }
+    public long TotalTime { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class JobPage {
+    public int TotalJobs { get; set; }
+    public IEnumerable<Job> Jobs { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs	(revision 12878)
@@ -0,0 +1,48 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Task {
+    public Guid Id { get; set; }
+    public Guid JobId { get; set; }
+    public string JobName { get; set; }
+    public long TotalTime { get; set; }
+    public double CalculatingTime { get; set; }
+    public double WaitingTime { get; set; }
+    public double TransferTime { get; set; }
+    public double InitialWaitingTime { get; set; }
+    public int NumCalculationRuns { get; set; }
+    public int NumRetries { get; set; }
+    public int CoresRequired { get; set; }
+    public int MemoryRequired { get; set; }
+    public int Priority { get; set; }
+    public string State { get; set; }
+    public Guid? LastClientId { get; set; }
+    public string LastClientName { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
+    public DateTime? StartTime { get; set; }
+    public DateTime? EndTime { get; set; }
+    public string Exception { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class TaskPage {
+    public int TotalTasks { get; set; }
+    public IEnumerable<Task> Tasks { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs	(revision 12878)
@@ -0,0 +1,27 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class TaskStateCount {
+    public string State { get; set; }
+    public int Count { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/User.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/User.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/User.cs	(revision 12878)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class User {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/UserDetails.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/UserDetails.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/UserDetails.cs	(revision 12878)
@@ -0,0 +1,31 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class UserDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ExceptionController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ExceptionController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ExceptionController.cs	(revision 12878)
@@ -0,0 +1,68 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+  [Authorize(Roles = HiveRoles.Administrator)]
+  public class ExceptionController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.ExceptionPage GetExceptions(int page, int size) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var dimJobDao = pm.DimJobDao;
+      var factTaskDao = pm.FactTaskDao;
+      var tasks = factTaskDao.GetTasksWithException();
+      return pm.UseTransaction(() => new DT.ExceptionPage {
+        TotalExceptions = tasks.Count(),
+        Exceptions = (from factTask in tasks
+                      join dimJob in dimJobDao.GetAll() on factTask.JobId equals dimJob.JobId
+                      join dimClient in dimClientDao.GetAll() on factTask.LastClientId equals
+                        dimClient.Id into taskClientJoin
+                      from a in taskClientJoin.DefaultIfEmpty()
+                      let endTime = factTask.EndTime ?? DateTime.Now
+                      select new DT.Exception {
+                        TaskId = factTask.TaskId,
+                        JobId = factTask.JobId,
+                        JobName = dimJob.JobName,
+                        UserId = dimJob.UserId,
+                        UserName = dimJob.UserName,
+                        ClientId = factTask.LastClientId ?? default(Guid),
+                        ClientName = a != null ? a.Name : string.Empty,
+                        Date = endTime,
+                        Details = factTask.Exception
+                      })
+                      .OrderByDescending(x => x.Date)
+                      .Skip((page - 1) * size)
+                      .Take(size)
+                      .ToList()
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/GroupController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/GroupController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/GroupController.cs	(revision 12878)
@@ -0,0 +1,165 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+  public class GroupController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.GroupDetails GetGroupDetails(Guid id) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var clientTimeData = factClientInfoDao.GetAll()
+          .Join(dimClientDao.GetAll(), x => x.ClientId, y => y.Id, (x, y) => new {
+            y.ResourceGroupId,
+            x.IdleTime,
+            x.OfflineTime,
+            x.UnavailableTime
+          })
+          .Where(x => x.ResourceGroupId == id)
+          .GroupBy(x => x.ResourceGroupId)
+          .Select(x => new {
+            TotalIdleTime = x.Sum(y => y.IdleTime),
+            TotalOfflineTime = x.Sum(y => y.OfflineTime),
+            TotalUnavailableTime = x.Sum(y => y.UnavailableTime)
+          })
+          .FirstOrDefault();
+
+        var taskTimeData = factTaskDao.GetByGroupId(id)
+          .Select(x => new {
+            id,
+            x.CalculatingTime,
+            x.TransferTime
+          })
+          .GroupBy(x => x.id)
+          .Select(x => new {
+            CalculatingTime = x.Sum(y => y.CalculatingTime),
+            TransferTime = x.Sum(y => y.TransferTime)
+          })
+          .FirstOrDefault();
+        return (from client in dimClientDao.GetActiveClients().Where(x => x.ResourceGroupId == id)
+                join info in factClientInfoDao.GetAll()
+                  on client.Id equals info.ClientId into clientInfoJoin
+                from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                let offline = (clientInfo.SlaveState == DA.SlaveState.Offline)
+                select new {
+                  ResourceGroupId = client.ResourceGroupId,
+                  GroupName = client.GroupName,
+                  TotalCores = clientInfo.NumTotalCores,
+                  UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                  TotalMemory = clientInfo.TotalMemory,
+                  UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                  CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                  SlaveState = clientInfo.SlaveState
+                })
+          .GroupBy(x => new { x.ResourceGroupId, x.GroupName })
+          .Select(x => new DT.GroupDetails {
+            Id = id,
+            Name = x.Key.GroupName,
+            TotalClients = x.Count(),
+            OnlineClients = x.Count(y => y.SlaveState != DA.SlaveState.Offline),
+            TotalCores = x.Sum(y => y.TotalCores),
+            UsedCores = x.Sum(y => y.UsedCores),
+            TotalMemory = x.Sum(y => y.TotalMemory),
+            UsedMemory = x.Sum(y => y.UsedMemory),
+            TotalCpuUtilization = x.Average(y => (double?)y.CpuUtilization) ?? 0.0,
+            ActiveCpuUtilization = x.Where(y => y.SlaveState != DA.SlaveState.Offline).Average(y => (double?)y.CpuUtilization) ?? 0.0,
+            TotalUnavailableTime = clientTimeData != null ? clientTimeData.TotalUnavailableTime : 0,
+            TotalCalculatingTime = taskTimeData != null ? taskTimeData.CalculatingTime : 0,
+            TotalIdleTime = clientTimeData != null ? clientTimeData.TotalIdleTime : 0,
+            TotalOfflineTime = clientTimeData != null ? clientTimeData.TotalOfflineTime : 0,
+            TotalTransferringTime = taskTimeData != null ? taskTimeData.TransferTime : 0,
+            TasksStates = factTaskDao.GetByGroupId(id)
+                                  .GroupBy(y => y.TaskState)
+                                  .Select(y => new DT.TaskStateCount {
+                                    State = y.Key.ToString(),
+                                    Count = y.Count()
+                                  }).ToList()
+          }).FirstOrDefault();
+      });
+    }
+
+    public DT.GroupPage GetGroups(int page, int size) {
+      var pm = PersistenceManager;
+      var dimClientDao = pm.DimClientDao;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var data = (from client in dimClientDao.GetActiveClients()
+                  join info in factClientInfoDao.GetAll()
+                    on client.Id equals info.ClientId into clientInfoJoin
+                  from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                  let offline = (clientInfo.SlaveState == DA.SlaveState.Offline)
+                  select new {
+                    ResourceGroupId = client.ResourceGroupId,
+                    TotalCores = clientInfo.NumTotalCores,
+                    UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                    TotalMemory = clientInfo.TotalMemory,
+                    UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                    CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                    SlaveState = clientInfo.SlaveState
+                  }).GroupBy(x => x.ResourceGroupId).Select(x => new {
+                    GroupId = x.Key,
+                    TotalClients = x.Count(),
+                    OnlineClients = x.Count(y => y.SlaveState != DA.SlaveState.Offline),
+                    TotalCores = x.Sum(y => y.TotalCores),
+                    UsedCores = x.Sum(y => y.UsedCores),
+                    TotalMemory = x.Sum(y => y.TotalMemory),
+                    UsedMemory = x.Sum(y => y.UsedMemory),
+                    CpuUtilization = x.Where(y => y.SlaveState != DA.SlaveState.Offline).Average(y => (double?)y.CpuUtilization) ?? 0.0
+                  });
+      var query = dimClientDao.GetAll()
+        .GroupBy(x => new { x.ResourceGroupId, x.GroupName })
+        .Select(x => new {
+          Id = x.Key.ResourceGroupId ?? default(Guid),
+          Name = x.Key.GroupName
+        });
+      return pm.UseTransaction(() => new DT.GroupPage {
+        TotalGroups = query.Count(),
+        Groups = query
+        .Skip((page - 1) * size)
+        .Take(size)
+        .Join(data, x => x.Id, y => y.GroupId,
+          (group, info) => new DT.Group {
+            Id = group.Id,
+            Name = group.Name,
+            TotalClients = info.TotalClients,
+            OnlineClients = info.OnlineClients,
+            TotalCores = info.TotalCores,
+            UsedCores = info.UsedCores,
+            TotalMemory = info.TotalMemory,
+            UsedMemory = info.UsedMemory,
+            CpuUtilization = info.CpuUtilization
+          }).ToList()
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs	(revision 12878)
@@ -0,0 +1,32 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+  public static class JavascriptUtils {
+    public static long ToTimestamp(DateTime input) {
+      var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+      var time = input.Subtract(new TimeSpan(epoch.Ticks));
+      return time.Ticks / 10000;
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs	(revision 12878)
@@ -0,0 +1,160 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Access;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize]
+  public class JobController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
+      get { return ServiceLocator.Instance.RoleVerifier; }
+    }
+
+    public DT.JobDetails GetJobDetails(Guid id) {
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var job = dimJobDao.GetById(id);
+        if (job != null) {
+          var timeData = factTaskDao.GetByJobId(id).GroupBy(x => x.JobId).Select(x => new {
+            AvgTransferringTime = (long)x.Average(y => y.TransferTime),
+            MinCalculatingTime = (long)x.Min(y => y.CalculatingTime),
+            MaxCalculatingTime = (long)x.Max(y => y.CalculatingTime),
+            AvgCalculatingTime = (long)x.Average(y => y.CalculatingTime),
+            TotalCalculatingTime = (long)x.Sum(y => y.CalculatingTime),
+            TotalWaitingTime = (long)x.Sum(y => y.WaitingTime)
+          }).FirstOrDefault();
+          DateTime endTime = job.DateCompleted ?? DateTime.Now;
+          long totalTime = (long)(endTime - job.DateCreated).TotalSeconds;
+          return new DT.JobDetails {
+            Id = job.JobId,
+            Name = job.JobName,
+            UserId = job.UserId,
+            UserName = job.UserName,
+            DateCreated = job.DateCreated,
+            DateCompleted = job.DateCompleted,
+            TotalTasks = job.TotalTasks,
+            CompletedTasks = job.CompletedTasks,
+            AvgTransferringTime = timeData != null ? timeData.AvgTransferringTime : 0,
+            AvgCalculatingTime = timeData != null ? timeData.AvgCalculatingTime : 0,
+            MinCalculatingTime = timeData != null ? timeData.MinCalculatingTime : 0,
+            MaxCalculatingTime = timeData != null ? timeData.MaxCalculatingTime : 0,
+            TotalCalculatingTime = timeData != null ? timeData.TotalCalculatingTime : 0,
+            TotalWaitingTime = timeData != null ? timeData.TotalWaitingTime : 0,
+            TotalTime = totalTime,
+            TasksStates = factTaskDao.GetByJobId(id)
+                          .GroupBy(x => x.TaskState)
+                          .Select(x => new DT.TaskStateCount {
+                            State = x.Key.ToString(),
+                            Count = x.Count()
+                          }).ToList()
+          };
+        }
+        return null;
+      });
+    }
+
+    public DT.JobPage GetJobs(int page, int size, bool completed = false) {
+      return GetJobsByUserId(UserManager.CurrentUserId, page, size, completed);
+    }
+
+    public DT.JobPage GetJobsByUserId(Guid id, int page, int size, bool completed = false) {
+      if (id != UserManager.CurrentUserId) {
+        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      }
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      return pm.UseTransaction(() => {
+        var jobs = dimJobDao.GetByUserId(id).Where(x => completed ? (x.DateCompleted != null) : (x.DateCompleted == null));
+        return new DT.JobPage {
+          TotalJobs = jobs.Count(),
+          Jobs = jobs.OrderByDescending(x => x.DateCompleted ?? DateTime.MaxValue)
+            .Skip((page - 1) * size)
+            .Take(size)
+            .Select(x => ConvertToDT(x))
+            .ToList()
+        };
+      });
+    }
+
+    public IEnumerable<DT.Job> GetAllJobs(bool completed) {
+      return GetAllJobsByUserId(UserManager.CurrentUserId, completed);
+    }
+
+    public IEnumerable<DT.Job> GetAllJobsByUserId(Guid id, bool completed) {
+      if (id != UserManager.CurrentUserId) {
+        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      }
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      return pm.UseTransaction(() => {
+        return dimJobDao.GetByUserId(id)
+          .Where(x => completed ? (x.DateCompleted != null) : (x.DateCompleted == null))
+          .Select(x => ConvertToDT(x))
+          .ToList();
+      });
+    }
+
+    [Authorize(Roles = HiveRoles.Administrator)]
+    public IEnumerable<DT.Job> GetAllActiveJobsFromAllUsers() {
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      return pm.UseTransaction(() => {
+        return dimJobDao.GetAll()
+          .Where(x => x.DateCompleted == null)
+          .OrderByDescending(x => x.DateCreated)
+          .Select(x => ConvertToDT(x))
+          .ToList();
+      });
+    }
+
+    private DT.Job ConvertToDT(DA.DimJob job) {
+      return new DT.Job {
+        Id = job.JobId,
+        Name = job.JobName,
+        UserId = job.UserId,
+        UserName = job.UserName,
+        DateCreated = job.DateCreated,
+        DateCompleted = job.DateCompleted,
+        TotalTasks = job.TotalTasks,
+        CompletedTasks = job.CompletedTasks
+      };
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs	(revision 12878)
@@ -0,0 +1,178 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Web.Http;
+using HeuristicLab.Services.Access;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+  [Authorize]
+  public class TaskController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
+      get { return ServiceLocator.Instance.RoleVerifier; }
+    }
+
+    [HttpPost]
+    public DT.TaskPage GetTasksByJobId(Guid id, int page, int size, IEnumerable<string> states) {
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      var dimClientDao = pm.DimClientDao;
+      var factTaskDao = pm.FactTaskDao;
+
+      DA.DimJob job = pm.UseTransaction(() => dimJobDao.GetById(id));
+      if (job == null) {
+        throw new ArgumentException("invalid job id");
+      }
+      if (job.UserId != UserManager.CurrentUserId) {
+        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      }
+
+      return pm.UseTransaction(() => {
+        var tasks = factTaskDao.GetByJobId(id).Where(x => states.Contains(x.TaskState.ToString()));
+        return new DT.TaskPage {
+          TotalTasks = tasks.Count(),
+          Tasks = (from factTask in tasks
+                   join dimJob in dimJobDao.GetAll() on factTask.JobId equals dimJob.JobId
+                   join dimClient in dimClientDao.GetAll() on factTask.LastClientId equals
+                     dimClient.Id into taskClientJoin
+                   from a in taskClientJoin.DefaultIfEmpty()
+                   let startTime = factTask.StartTime ?? DateTime.Now
+                   let endTime = factTask.EndTime ?? DateTime.Now
+                   select new DT.Task {
+                     Id = factTask.TaskId,
+                     JobId = factTask.JobId,
+                     JobName = dimJob.JobName,
+                     TotalTime = (long)(endTime - startTime).TotalSeconds,
+                     CalculatingTime = factTask.CalculatingTime,
+                     WaitingTime = factTask.WaitingTime,
+                     TransferTime = factTask.TransferTime,
+                     InitialWaitingTime = factTask.InitialWaitingTime,
+                     NumCalculationRuns = factTask.NumCalculationRuns,
+                     NumRetries = factTask.NumRetries,
+                     CoresRequired = factTask.CoresRequired,
+                     MemoryRequired = factTask.MemoryRequired,
+                     Priority = factTask.Priority,
+                     State = factTask.TaskState.ToString(),
+                     LastClientId = factTask.LastClientId,
+                     LastClientName = a != null ? a.Name : string.Empty,
+                     UserId = dimJob.UserId,
+                     UserName = dimJob.UserName,
+                     StartTime = factTask.StartTime,
+                     EndTime = factTask.EndTime,
+                     Exception = factTask.Exception
+                   })
+            .Skip((page - 1) * size)
+            .Take(size)
+            .ToList()
+        };
+      });
+    }
+
+    [HttpPost]
+    public DT.TaskPage GetTasksByClientId(Guid id, int page, int size, IEnumerable<string> states, Guid userId = default(Guid)) {
+      bool isAdministrator = User.IsInRole(HiveRoles.Administrator);
+      var pm = PersistenceManager;
+      var dimJobDao = pm.DimJobDao;
+      var dimClientDao = pm.DimClientDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var tasks = factTaskDao.GetByClientId(id).Where(x => states.Contains(x.TaskState.ToString()));
+        if (userId != Guid.Empty) {
+          tasks = tasks.Where(x => x.DimJob.UserId == userId);
+        }
+        return new DT.TaskPage {
+          TotalTasks = tasks.Count(),
+          Tasks = (from factTask in tasks
+                   join dimJob in dimJobDao.GetAll() on factTask.JobId equals dimJob.JobId
+                   join dimClient in dimClientDao.GetAll() on factTask.LastClientId equals
+                     dimClient.Id into taskClientJoin
+                   from a in taskClientJoin.DefaultIfEmpty()
+                   let startTime = factTask.StartTime ?? DateTime.Now
+                   let endTime = factTask.EndTime ?? DateTime.Now
+                   select new DT.Task {
+                     Id = isAdministrator ? factTask.TaskId : default(Guid),
+                     JobId = isAdministrator ? factTask.JobId : default(Guid),
+                     JobName = isAdministrator ? dimJob.JobName : string.Empty,
+                     TotalTime = (long)(endTime - startTime).TotalSeconds,
+                     CalculatingTime = factTask.CalculatingTime,
+                     WaitingTime = factTask.WaitingTime,
+                     TransferTime = factTask.TransferTime,
+                     InitialWaitingTime = factTask.InitialWaitingTime,
+                     NumCalculationRuns = factTask.NumCalculationRuns,
+                     NumRetries = factTask.NumRetries,
+                     CoresRequired = factTask.CoresRequired,
+                     MemoryRequired = factTask.MemoryRequired,
+                     Priority = factTask.Priority,
+                     State = factTask.TaskState.ToString(),
+                     LastClientId = factTask.LastClientId,
+                     LastClientName = a != null ? a.Name : string.Empty,
+                     UserId = isAdministrator ? dimJob.UserId : default(Guid),
+                     UserName = isAdministrator ? dimJob.UserName : string.Empty,
+                     StartTime = factTask.StartTime,
+                     EndTime = factTask.EndTime,
+                     Exception = isAdministrator ? factTask.Exception : string.Empty
+                   })
+                .OrderByDescending(x => x.EndTime ?? DateTime.MaxValue)
+                .Skip((page - 1) * size)
+                .Take(size)
+                .ToList()
+        };
+      });
+    }
+
+    public HttpResponseMessage GetTaskDataById(Guid id) {
+      var pm = PersistenceManager;
+      var taskDataDao = pm.TaskDataDao;
+      return pm.UseTransaction(() => {
+        var taskData = taskDataDao.GetById(id);
+        if (taskData == null)
+          return new HttpResponseMessage(HttpStatusCode.NotFound);
+        HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
+        var stream = new MemoryStream(taskData.Data);
+        result.Content = new StreamContent(stream);
+        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
+        result.Content.Headers.ContentDisposition =
+          new ContentDispositionHeaderValue("attachment") {
+            FileName = string.Format("{0}.hl", id)
+          };
+        return result;
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs	(revision 12878)
@@ -0,0 +1,71 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 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 <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize(Roles = HiveRoles.Administrator)]
+  public class UserController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public IEnumerable<DT.User> GetUsers() {
+      var pm = PersistenceManager;
+      var dimUserDao = pm.DimUserDao;
+      return pm.UseTransaction(() => {
+        return dimUserDao.GetAll().Select(x => new DT.User {
+          Id = x.UserId,
+          Name = x.Name
+        }).ToList();
+      });
+    }
+
+    public DT.UserDetails GetUser(Guid id) {
+      var pm = PersistenceManager;
+      var dimUserDao = pm.DimUserDao;
+      var factTaskDao = pm.FactTaskDao;
+      return pm.UseTransaction(() => {
+        var user = dimUserDao.GetById(id);
+        if (user != null) {
+          return new DT.UserDetails {
+            Id = user.UserId,
+            Name = user.Name,
+            TasksStates = factTaskDao.GetByUserId(id)
+                          .GroupBy(x => x.TaskState)
+                          .Select(x => new DT.TaskStateCount {
+                            State = x.Key.ToString(),
+                            Count = x.Count()
+                          }).ToList()
+          };
+        }
+        return null;
+      });
+    }
+  }
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml	(revision 12878)
@@ -0,0 +1,123 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li class="active">
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Current Clients</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Client Name</th>
+                            <th>Group Name</th>
+                            <th>Cores</th>
+                            <th>Cpu Utilization</th>
+                            <th>Memory</th>
+                            <th>State</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tr ng-repeat="client in clientPage.Clients">
+                            <td>{{($index + 1)+((curClientsPage-1)*(clientsPageSize))}}</td>
+                            <td>{{client.Name}}</td>
+                            <td>
+                                <a ng-show="client.GroupName" ng-href="#/statistics/groups/{{client.GroupId}}">{{client.GroupName}}</a>
+                                <span ng-hide="client.GroupName">No Group</span>
+                            </td>
+                            <td>{{client.UsedCores}} / {{client.TotalCores}}</td>
+                            <td>{{client.CpuUtilization | number: 2}} %</td>
+                            <td>{{client.UsedMemory | kbToGB}} / {{client.TotalMemory | kbToGB}} GB</td>
+                            <td>{{client.State}}</td>
+                            <td>
+                                <a ng-href="#/statistics/clients/{{client.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="clientPage.Clients.length">
+                            <td colspan="7" class="text-center">No clients found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="clientPage.TotalClients > clientPage.Clients.length">
+                        <pagination max-size="10" total-items="clientPage.TotalClients" ng-model="curClientsPage" ng-change="changeClientsPage()" items-per-page="clientsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Expired Clients</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Client Name</th>
+                                <th>Group Name</th>
+                                <th>Cores</th>
+                                <th>Cpu Utilization</th>
+                                <th>Memory</th>
+                                <th>State</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="client in expiredClientPage.Clients">
+                            <td>{{($index + 1)+((curExpiredClientsPage-1)*(expiredClientsPageSize))}}</td>
+                            <td>{{client.Name}}</td>
+                            <td>
+                                <span ng-show="client.GroupName">{{client.GroupName}}</span>
+                                <span ng-hide="client.GroupName">No Group</span>
+                            </td>
+                            <td>{{client.UsedCores}} / {{client.TotalCores}}</td>
+                            <td>{{client.CpuUtilization | number: 2}} %</td>
+                            <td>{{client.UsedMemory | kbToGB}} / {{client.TotalMemory | kbToGB}} GB</td>
+                            <td>{{client.State}}</td>
+                            <td>
+                                <a ng-href="#/statistics/clients/{{client.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="expiredClientPage.Clients.length">
+                            <td colspan="7" class="text-center">No expired clients found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="expiredClientPage.TotalClients > expiredClientPage.Clients.length">
+                        <pagination max-size="10" total-items="expiredClientPage.TotalClients" ng-model="curExpiredClientsPage" ng-change="changeExpiredClientsPage()" items-per-page="expiredClientsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js	(revision 12878)
@@ -0,0 +1,50 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.clientsCtrl',
+        ['$scope', '$interval', 'app.statistics.clientService',
+        function ($scope, $interval, clientService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curClientsPage = 1;
+            $scope.clientsPageSize = 20;
+            $scope.curExpiredClientsPage = 1;
+            $scope.expiredClientsPageSize = 20;
+
+            var getClients = function () {
+                clientService.getClients({ page: $scope.curClientsPage, size: $scope.clientsPageSize, expired: false },
+                    function (clientPage) {
+                        $scope.clientPage = clientPage;
+                    }
+                );
+            };
+
+            var getExpiredClients = function() {
+                clientService.getClients({ page: $scope.curExpiredClientsPage, size: $scope.expiredClientsPageSize, expired: true },
+                    function (clientPage) {
+                        $scope.expiredClientPage = clientPage;
+                    }
+                );
+            };
+
+            var update = function () {
+                getClients();
+                getExpiredClients();
+            };
+
+            $scope.changeClientsPage = function () {
+                update();
+            };
+
+            $scope.changeExpiredClientsPage = function () {
+                update();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml	(revision 12878)
@@ -0,0 +1,369 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li class="active">
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Client Details</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="row">
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Name:</td>
+                                    <td>{{client.Name}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Group:</td>
+                                    <td ng-show="client.GroupName"><a ng-href="#/statistics/groups/{{client.GroupId}}">{{client.GroupName}}</a></td>
+                                    <td ng-hide="client.GroupName">None</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">State:</td>
+                                    <td>{{client.State}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Up Time:</td>
+                                    <td>{{client.UpTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Last Update:</td>
+                                    <td>{{client.LastUpdate | toDate}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Total Unavailable Time:</td>
+                                    <td class="text-right">{{client.TotalUnavailableTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Calculating Time:</td>
+                                    <td class="text-right">{{client.TotalCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Idle Time:</td>
+                                    <td class="text-right">{{client.TotalIdleTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Transferring Time:</td>
+                                    <td class="text-right">{{client.TotalTransferringTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Offline Time:</td>
+                                    <td class="text-right">{{client.TotalOfflineTime | toTimespan}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalClientTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in states">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Cores</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cores" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{client.TotalCores | number}}</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{client.UsedCores | number}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">CPU Utilization</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cpu" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">&nbsp;</td>
+                            <td class="text-right">&nbsp;</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">&nbsp;</td>
+                            <td class="text-right">&nbsp;</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Memory</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.memory" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{client.TotalMemory | kbToGB}} GB</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{client.UsedMemory | kbToGB}} GB</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="default-filter-header text-center">
+    <form class="form-inline">
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">From:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <label for="fromDate">To:&nbsp;</label>
+            <div class="input-group">
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="toDate" is-open="toIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close"/>
+                <span class="input-group-btn">
+                    <button type="button" class="btn btn-default" ng-click="openToDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                </span>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <div class="btn-group" dropdown dropdown-append-to-body>
+                <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width: 145px; text-align: left;">
+                    <span class="text-left" style="display: inline-block; width: 100px;">{{curQuickSelection.name}}</span>
+                    <span class="glyphicon glyphicon-chevron-down" style="margin-top: 3px;"></span>
+                </button>
+                <ul class="dropdown-menu" role="menu">
+                    <li ng-repeat="quickSelection in quickSelectionList">
+                        <a class="cursor-pointer" ng-click="changeQuickSelection(quickSelection)">{{quickSelection.name}}</a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <button type="button" class="btn btn-default" ng-click="updateCharts()">
+                Apply
+            </button>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">CPU Utilization Chart</h3>
+                </div>
+                <div class="panel-body">
+                    <flot dataset="cpuSeries" options="chartOptions"></flot>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Core Chart</h3>
+                </div>
+                <div class="panel-body">
+                    <flot dataset="coreSeries" options="fillChartOptions"></flot>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Memory Chart</h3>
+                </div>
+                <div class="panel-body">
+                    <flot dataset="memorySeries" options="fillChartOptions"></flot>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="tasks-filter" class="default-filter-header text-center">
+    <form class="form-inline">
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <div class="btn-group" data-toggle="buttons">
+                <label class="btn btn-default" ng-class="{active: state.Selected}" ng-repeat="state in states"
+                       ng-click="filterTaskState(state)">
+                    <input type="checkbox" autocomplete="off" checked> {{state.State}}
+                </label>
+            </div>
+        </div>
+        <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+            <div class="btn-group" dropdown dropdown-append-to-body>
+                <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle>
+                    {{curUserName}} <span class="caret"></span>
+                </button>
+                <ul class="dropdown-menu" role="menu">
+                    <li>
+                        <a ng-href="#/statistics/clients/{{client.Id}}"
+                           ng-click="userChanged('00000000-0000-0000-0000-000000000000', 'All Users')">All Users</a>
+                    </li>
+                    <li class="divider"></li>
+                    <li ng-repeat="user in client.Users">
+                        <a ng-href="#/statistics/clients/{{client.Id}}"
+                           ng-click="userChanged(user.Id, user.Name)">{{user.Name}}</a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div id="taskListContainer" class="row" ng-show="taskPage.Tasks.length">
+        <div class="col-lg-4 col-md-4" ng-repeat="task in taskPage.Tasks">
+            <div class="panel panel-default">
+                @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                   <div class="panel-heading cursor-pointer" ng-click="openDialog(($index + 1)+((curTaskPage-1)*(taskPageSize)), task)">
+                       <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                       <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                   </div>
+                } else {
+                   <div class="panel-heading">
+                       <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                       <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                       <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                   </div>
+                }
+                
+                <div class="panel-body">
+                    <table class="table table-condensed table-no-border table-auto-width">
+                        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                            <tr>
+                                <td colspan="2"><span data-fittext="0.99">{{task.Id}}</span></td>
+                            </tr>
+                        }
+                       <tr>
+                           <td>Start Time:</td>
+                           <td ng-show="task.StartTime" class="text-right">{{task.StartTime | toDate}}</td>
+                           <td ng-hide="task.StartTime" class="text-right">Not started</td>
+                       </tr>
+                       <tr>
+                           <td>End Time:</td>
+                           <td ng-show="task.EndTime" class="text-right">{{task.EndTime | toDate}}</td>
+                           <td ng-hide="task.EndTime" class="text-right">Not finished</td>
+                       </tr>
+                        <tr>
+                            <td>Calculating Time:</td>
+                            <td class="text-right">{{task.CalculatingTime | toTimespan}}</td>
+                        </tr>
+                        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                            <tr>
+                                <td>User:</td>
+                                <td class="text-right">
+                                    <a ng-href="#/statistics/users/{{task.UserId}}" ng-show="task.UserName">{{task.UserName}}</a>
+                                    <span ng-hide="task.UserName">Unknown</span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>Job:</td>
+                                <td class="text-right">
+                                    <a ng-href="#/statistics/jobs/{{task.JobId}}" ng-show="task.JobName">{{task.JobName}}</a>
+                                    <span ng-hide="task.JobName">Unknown</span>
+                                </td>
+                            </tr>
+                        }
+                    </table>
+                </div>
+
+            </div>
+        </div>
+    </div>
+    <div ng-hide="taskPage.Tasks.length">
+        <p class="text-center">Please select at least one state!</p>
+    </div>
+    <div class="row text-center" ng-show="taskPage.TotalTasks > taskPage.Tasks.length">
+        <pagination max-size="10" total-items="taskPage.TotalTasks" ng-model="curTaskPage" ng-change="changeTaskPage()" items-per-page="taskPageSize"
+                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+    </div>
+</div>
+
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js	(revision 12878)
@@ -0,0 +1,288 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.clientDetailsCtrl',
+        ['$scope', '$stateParams', '$interval', 'app.statistics.clientService', 'app.statistics.taskService', '$modal',
+        function ($scope, $stateParams, $interval, clientService, taskService, $modal) {
+            $scope.curUserId = '00000000-0000-0000-0000-000000000000';
+            $scope.curUserName = 'All Users';
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curTaskPage = 1;
+            $scope.taskPageSize = 12;
+
+            // details
+            $scope.knobOptions = {
+                'fgColor': "#f7921d",
+                'angleOffset': -125,
+                'angleArc': 250,
+                'readOnly': true,
+                'width': "80%",
+                'targetvalue': "100",
+                'format': function (value) {
+                    return value;
+                },
+                draw: function () {
+                    $(this.i).val(this.cv + '%');
+                }
+            };
+
+            $scope.knobData = {
+                cores: 0,
+                cpu: 0,
+                memory: 0
+            };
+
+            var getClientDetails = function () {
+                clientService.getClientDetails({ id: $stateParams.id }, function (client) {
+                    $scope.client = client;
+                    $scope.knobData.cores = (client.UsedCores / client.TotalCores) * 100;
+                    $scope.knobData.cpu = client.CpuUtilization;
+                    $scope.knobData.memory = (client.UsedMemory / client.TotalMemory) * 100;
+
+                    var length = client.TasksStates.length;
+                    var total = 0;
+                    var jsStates = [];
+                    for (var i = 0; i < length; ++i) {
+                        var state = client.TasksStates[i];
+                        var selected = true;
+                        if (isDefined($scope.states)) {
+                            for (var j = 0; j < $scope.states.length; ++j) {
+                                if (state.State == $scope.states[j].State) {
+                                    selected = $scope.states[j].Selected;
+                                    break;
+                                }
+                            }
+                        }
+                        jsStates.push({
+                            State: state.State,
+                            Count: state.Count,
+                            Selected: selected
+                        });
+                        total += state.Count;
+                    }
+                    $scope.totalClientTasks = total;
+                    $scope.states = jsStates;
+                    getTasks();
+                });
+            };
+
+            // tasks
+            var getTasks = function () {
+                var states = [];
+                var length = $scope.states.length;
+                for (var i = 0; i < length; ++i) {
+                    var state = $scope.states[i];
+                    if (state.Selected) {
+                        states.push(state.State);
+                    }
+                }
+
+                taskService.getTasksByClientId({ id: $stateParams.id, page: $scope.curTaskPage, size: $scope.taskPageSize, userId: $scope.curUserId }, states,
+                    function (taskPage) {
+                        $scope.taskPage = taskPage;
+                    }
+                );
+            };
+
+            $scope.changeTaskPage = function () {
+                getClientDetails();
+                $('html, body').animate({
+                    scrollTop: $("#tasks-filter").offset().top
+                }, 10);
+            };
+
+            $scope.filterTaskState = function (state) {
+                state.Selected = !state.Selected;
+                $scope.curTaskPage = 1;
+                getClientDetails();
+            };
+
+            $scope.userChanged = function(id, name) {
+                $scope.curUserId = id;
+                $scope.curUserName = name;
+                $scope.curTaskPage = 1;
+                getClientDetails();
+            };
+
+            $scope.openDialog = function (taskNo, task) {
+                $scope.currentTaskNo = taskNo;
+                $scope.currentTask = task;
+                $modal.open({
+                    templateUrl: 'plugin=statistics&view=WebApp/clients/details/clientTaskDetailsDialog.cshtml',
+                    controller: 'app.statistics.clientTaskDetailsDialogCtrl',
+                    windowClass: 'app-modal-window',
+                    resolve: {
+                        task: function () {
+                            return $scope.currentTask;
+                        },
+                        taskNo: function () {
+                            return $scope.currentTaskNo;
+                        }
+                    }
+                });
+            };
+
+            // charts
+            $scope.chartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0
+                },
+                yaxis: {
+                    min: 0,
+                    max: 100,
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fillChartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0,
+                    lines: {
+                        show: true,
+                        fill: true
+                    }
+                },
+                yaxis: {
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fromDate = new Date();
+            $scope.toDate = new Date();
+
+            $scope.fromIsOpen = false;
+            $scope.toIsOpen = false;
+
+            $scope.quickSelectionList = [
+                { id: 0, name: 'Custom' },
+                { id: 1, name: 'Today' },
+                { id: 2, name: 'Yesterday' },
+                { id: 3, name: 'Last 7 Days' },
+                { id: 4, name: 'Last 30 Days' },
+                { id: 5, name: 'Last 6 Months' },
+                { id: 6, name: 'Last Year' }
+            ];
+            $scope.changeQuickSelection = function (quickSelection) {
+                var today = new Date();
+                var oneDayInMs = 24 * 60 * 60 * 1000;
+                switch (quickSelection.id) {
+                    case 1:
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 2:
+                        $scope.fromDate = new Date(today.valueOf() - oneDayInMs);
+                        $scope.toDate = new Date(today.valueOf() - oneDayInMs);
+                        break;
+                    case 3:
+                        $scope.fromDate = new Date(today.valueOf() - (7 * oneDayInMs));
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 4:
+                        $scope.fromDate = new Date(today.valueOf() - (30 * oneDayInMs));
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 5:
+                        var month = today.getMonth() - 6;
+                        if (month < 0) {
+                            month += 12;
+                        }
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setMonth(month);
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 6:
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setFullYear(today.getFullYear() - 1);
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                }
+                $scope.curQuickSelection = quickSelection;
+            };
+            // set default 'today'
+            $scope.changeQuickSelection($scope.quickSelectionList[1]);
+
+            $scope.openFromDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.toIsOpen = false;
+                $scope.fromIsOpen = true;
+                $scope.curQuickSelection = $scope.quickSelectionList[0];
+            };
+
+            $scope.openToDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.fromIsOpen = false;
+                $scope.toIsOpen = true;
+                $scope.curQuickSelection = $scope.quickSelectionList[0];
+            };
+
+            $scope.dateOptions = {
+                formatYear: 'yy',
+                startingDay: 1
+            };
+
+            $scope.cpuSeries = [[]];
+            $scope.coreSeries = [[]];
+            $scope.memorySeries = [[]];
+
+            $scope.updateCharts = function () {
+                clientService.getClientHistory({ id: $stateParams.id, start: ConvertFromDate($scope.fromDate), end: ConvertToDate($scope.toDate) }, function (status) {
+                    var noOfStatus = status.length;
+                    var cpuSeries = [];
+                    var coreSeries = [[], []];
+                    var memorySeries = [[], []];
+                    for (var i = 0; i < noOfStatus; ++i) {
+                        var curStatus = status[i];
+                        var cpuData = Math.round(curStatus.CpuUtilization);
+                        cpuSeries.push([curStatus.Timestamp, cpuData]);
+                        coreSeries[0].push([curStatus.Timestamp, curStatus.TotalCores]);
+                        coreSeries[1].push([curStatus.Timestamp, curStatus.UsedCores]);
+                        memorySeries[0].push([curStatus.Timestamp, curStatus.TotalMemory]);
+                        memorySeries[1].push([curStatus.Timestamp, curStatus.UsedMemory]);
+                    }
+                    $scope.cpuSeries = [{ data: cpuSeries, label: "&nbsp;CPU Utilization", color: "#f7921d" }];
+                    $scope.coreSeries = [
+                        { data: coreSeries[0], label: "&nbsp;Total Cores", color: "LightGreen" },
+                        { data: coreSeries[1], label: "&nbsp;Used Cores", color: "LightPink" }
+                    ];
+                    $scope.memorySeries = [
+                        { data: memorySeries[0], label: "&nbsp;Total Memory", color: "LightGreen" },
+                        { data: memorySeries[1], label: "&nbsp;Used Memory", color: "LightPink" }
+                    ];
+
+                });
+            };
+
+            var update = function () {
+                getClientDetails();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+            $scope.updateCharts();
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml	(revision 12878)
@@ -0,0 +1,125 @@
+﻿@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<div class="modal-header">
+    <button type="button" ng-click="close()" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    <h4 class="modal-title">Task #{{taskNo}} - {{task.Id}}</h4>
+</div>
+<div class="modal-body" style="height: 440px">
+    <tabset>
+        <tab heading="Details">
+            <div style="height: 340px" class="center-block">
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Start Time:</td>
+                                <td ng-show="task.StartTime" class="text-left">{{task.StartTime | toDate}}</td>
+                                <td ng-hide="task.StartTime" class="text-left">Not started</td>
+                            </tr>
+                            <tr>
+                                <td>End Time:</td>
+                                <td ng-show="task.EndTime" class="text-left">{{task.EndTime | toDate}}</td>
+                                <td ng-hide="task.EndTime" class="text-left">Not finished</td>
+                            </tr>
+                            <tr>
+                                <td>State:</td>
+                                <td>{{task.State}}</td>
+                            </tr>
+                        </table>
+                    </div>
+
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Slave:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                    <span ng-hide="task.LastClientName">None</span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>User:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/users/{{task.UserId}}" ng-show="task.UserName">{{task.UserName}}</a>
+                                    <span ng-hide="task.UserName">Unknown</span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>Job:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/jobs/{{task.JobId}}" ng-show="task.JobName">{{task.JobName}}</a>
+                                    <span ng-hide="task.JobName">Unknown</span>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Total Time:</td>
+                                <td>{{task.TotalTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Initial Waiting Time:</td>
+                                <td>{{task.InitialWaitingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Waiting Time:</td>
+                                <td>{{task.WaitingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Calculating Time:</td>
+                                <td>{{task.CalculatingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Transfer Time:</td>
+                                <td>{{task.TransferTime | toTimespan}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Calculation Runs:</td>
+                                <td class="text-right">{{task.NumCalculationRuns}}</td>
+                            </tr>
+                            <tr>
+                                <td>Retries Runs:</td>
+                                <td class="text-right">{{task.NumRetries}}</td>
+                            </tr>
+                            <tr>
+                                <td>Cores Required:</td>
+                                <td class="text-right">{{task.CoresRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Memory Required:</td>
+                                <td class="text-right">{{task.MemoryRequired}} MB</td>
+                            </tr>
+                            <tr>
+                                <td>Priority:</td>
+                                <td class="text-right">{{task.Priority}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="col-md-12">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td><a href="api/Statistics/Task/GetTaskDataById?id={{task.Id}}">Download</a></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </tab>
+        <tab heading="Exception" ng-show="task.Exception">
+            <div style="overflow: auto;">
+                <p style="word-wrap: break-word;">{{task.Exception}}</p>
+            </div>
+        </tab>
+    </tabset>
+</div>
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.clientTaskDetailsDialogCtrl',
+        ['$scope', '$modalInstance', 'taskNo', 'task', function ($scope, $modalInstance, taskNo, task) {
+            $scope.taskNo = taskNo;
+            $scope.task = task;
+            $scope.close = function () {
+                $modalInstance.dismiss('cancel');
+            };
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialog.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialog.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialog.cshtml	(revision 12878)
@@ -0,0 +1,9 @@
+﻿<div class="modal-header">
+    <button type="button" ng-click="close()" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    <h4 class="modal-title">Exception Details</h4>
+</div>
+<div class="modal-body">
+    <div style="overflow:auto;">
+        <p style="word-wrap: break-word;">{{message}}</p>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialogCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialogCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionDetailsDialogCtrl.js	(revision 12878)
@@ -0,0 +1,11 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.exceptionDetailsDialogCtrl',
+        ['$scope', '$modalInstance', 'message', function ($scope, $modalInstance, message) {
+            $scope.message = message;
+            $scope.close = function () {
+                $modalInstance.dismiss('cancel');
+            };
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptions.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptions.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptions.cshtml	(revision 12878)
@@ -0,0 +1,66 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/users">Users</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/exceptions">Exceptions</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Groups</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Job</th>
+                            <th>Task</th>
+                            <th>Username</th>
+                            <th>Client</th>
+                            <th>Date</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tr ng-repeat="exception in exceptionPage.Exceptions">
+                            <td>{{($index + 1)+((curExceptionsPage-1)*(exceptionsPageSize))}}</td>
+                            <td><a ng-href="#/statistics/jobs/{{exception.JobId}}">{{exception.JobName}}</a></td>
+                            <td>{{exception.TaskId}}</td>
+                            <td><a ng-href="#/statistics/users/{{exception.UserId}}">{{exception.UserName}}</a></td>
+                            <td><a ng-href="#/statistics/clients/{{exception.ClientId}}">{{exception.ClientName}}</a></td>
+                            <td>{{exception.Date | toDate}}</td>
+                            <td><a class="cursor-pointer" ng-click="openDialog(exception.Details)">Details</a></td>
+                        </tr>
+                        <tr ng-hide="exceptionPage.Exceptions.length">
+                            <td colspan="7" class="text-center">No exceptions found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="exceptionPage.TotalExceptions > exceptionPage.Exceptions.length">
+                        <pagination max-size="10" total-items="exceptionPage.TotalExceptions" ng-model="curExceptionsPage" ng-change="changeExceptionsPage()"
+                                    items-per-page="exceptionsPageSize" boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/exceptions/exceptionsCtrl.js	(revision 12878)
@@ -0,0 +1,49 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.exceptionsCtrl',
+        ['$scope', '$interval', '$modal', 'app.statistics.exceptionService',
+            function ($scope, $interval, $modal, exceptionService) {
+                $scope.interval = defaultPageUpdateInterval;
+                $scope.curExceptionsPage = 1;
+                $scope.exceptionsPageSize = 20;
+
+                var getExceptions = function () {
+                    exceptionService.getExceptions({ page: $scope.curExceptionsPage, size: $scope.exceptionsPageSize },
+                        function (exceptionPage) {
+                            $scope.exceptionPage = exceptionPage;
+                        }
+                    );
+                };
+
+                $scope.changeExceptionsPage = function () {
+                    update();
+                };
+
+                var update = function () {
+                    getExceptions();
+                };
+
+                $scope.openDialog = function (message) {
+                    $scope.message = message;
+                    $modal.open({
+                        templateUrl: 'plugin=statistics&view=WebApp/exceptions/exceptionDetailsDialog.cshtml',
+                        controller: 'app.statistics.exceptionDetailsDialogCtrl',
+                        windowClass: 'app-modal-window',
+                        resolve: {
+                            message: function () {
+                                return $scope.message;
+                            }
+                        }
+                    });
+                };
+
+                $scope.updateInterval = $interval(update, $scope.interval);
+                var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                    $interval.cancel($scope.updateInterval);
+                    cancelInterval();
+                });
+                update();
+            }
+        ]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetails.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetails.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetails.cshtml	(revision 12878)
@@ -0,0 +1,292 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li> 
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Group Details</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="row">
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Name:</td>
+                                    <td class="text-right">{{group.Name}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Clients:</td>
+                                    <td class="text-right">{{group.OnlineClients}} / {{group.TotalClients}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Total Unavailable Time:</td>
+                                    <td class="text-right">{{group.TotalUnavailableTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Calculating Time:</td>
+                                    <td class="text-right">{{group.TotalCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Idle Time:</td>
+                                    <td class="text-right">{{group.TotalIdleTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Transferring Time:</td>
+                                    <td class="text-right">{{group.TotalTransferringTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Offline Time:</td>
+                                    <td class="text-right">{{group.TotalOfflineTime | toTimespan}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalGroupTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in group.TasksStates">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Cores</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cores" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{group.TotalCores | number}}</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{group.UsedCores | number}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">CPU Utilization</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cpu" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{group.TotalCpuUtilization | number: 2}} %</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Active:</td>
+                            <td class="text-right">{{group.ActiveCpuUtilization | number: 2}} %</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Memory</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.memory" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{group.TotalMemory | kbToGB}} GB</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{group.UsedMemory | kbToGB}} GB</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Clients</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Client Name</th>
+                                <th>Cores</th>
+                                <th>Cpu Utilization</th>
+                                <th>Memory</th>
+                                <th>State</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="client in clientPage.Clients">
+                            <td>{{($index + 1)+((curClientsPage-1)*(clientsPageSize))}}</td>
+                            <td>{{client.Name}}</td>
+                            <td>{{client.UsedCores}} / {{client.TotalCores}}</td>
+                            <td>{{client.CpuUtilization | number: 2}} %</td>
+                            <td>{{client.UsedMemory | kbToGB}} / {{client.TotalMemory | kbToGB}}</td>
+                            <td>{{client.State}}</td>
+                            <td>
+                                <a ng-href="#/statistics/clients/{{client.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="clientPage.Clients.length">
+                            <td colspan="7" class="text-center">No clients found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="clientPage.TotalClients > clientPage.Clients.length">
+                        <pagination max-size="10" total-items="clientPage.TotalClients" ng-model="curClientsPage" ng-change="changeClientsPage()" items-per-page="clientsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+ </div>
+
+ <div class="default-filter-header text-center">
+     <form class="form-inline">
+         <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+             <label for="fromDate">From:&nbsp;</label>
+             <div class="input-group">
+                 <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                 <span class="input-group-btn">
+                     <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                 </span>
+             </div>
+         </div>
+         <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+             <label for="fromDate">To:&nbsp;</label>
+             <div class="input-group">
+                 <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="toDate" is-open="toIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close"/>
+                 <span class="input-group-btn">
+                     <button type="button" class="btn btn-default" ng-click="openToDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                 </span>
+             </div>
+         </div>
+         <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+             <div class="btn-group" dropdown dropdown-append-to-body>
+                 <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width: 145px; text-align: left;">
+                     <span class="text-left" style="display: inline-block; width: 100px;">{{curQuickSelection.name}}</span>
+                     <span class="glyphicon glyphicon-chevron-down" style="margin-top: 3px;"></span>
+                 </button>
+                 <ul class="dropdown-menu" role="menu">
+                     <li ng-repeat="quickSelection in quickSelectionList">
+                         <a class="cursor-pointer" ng-click="changeQuickSelection(quickSelection)">{{quickSelection.name}}</a>
+                     </li>
+                 </ul>
+             </div>
+         </div>
+         <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
+             <button type="button" class="btn btn-default" ng-click="updateCharts()">
+                 Apply
+             </button>
+         </div>
+     </form>
+ </div>
+
+ <div class="default-view-container">
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">CPU Utilization Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="cpuSeries" options="chartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">Core Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="coreSeries" options="fillChartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">Memory Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="memorySeries" options="fillChartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+ </div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetailsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetailsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/details/groupDetailsCtrl.js	(revision 12878)
@@ -0,0 +1,222 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.groupDetailsCtrl',
+        ['$scope', '$stateParams', '$interval', 'app.statistics.clientService', 'app.statistics.groupService',
+        function ($scope, $stateParams, $interval, clientService, groupService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curClientsPage = 1;
+            $scope.clientsPageSize = 20;
+
+
+            // details
+            $scope.knobOptions = {
+                'fgColor': "#f7921d",
+                'angleOffset': -125,
+                'angleArc': 250,
+                'readOnly': true,
+                'width': "80%",
+                'targetvalue': "100",
+                'format': function (value) {
+                    return value;
+                },
+                draw: function () {
+                    $(this.i).val(this.cv + '%');
+                }
+            };
+
+            $scope.knobData = {
+                cores: 0,
+                cpu: 0,
+                memory: 0
+            };
+
+            var getGroupDetails = function() {
+                groupService.getGroupDetails({ id: $stateParams.id }, function (group) {
+                    $scope.group = group;
+                    $scope.knobData.cores = (group.UsedCores / group.TotalCores) * 100;
+                    $scope.knobData.cpu = group.ActiveCpuUtilization;
+                    $scope.knobData.memory = (group.UsedMemory / group.TotalMemory) * 100;
+
+                    var length = group.TasksStates.length;
+                    var total = 0;
+                    for (var i = 0; i < length; ++i) {
+                        total += group.TasksStates[i].Count;
+                    }
+                    $scope.totalGroupTasks = total;
+                });
+            };
+
+            var getClients = function () {
+                clientService.getClientsByGroupId({id: $stateParams.id, page: $scope.curClientsPage, size: $scope.clientsPageSize, expired: false },
+                    function (clientPage) {
+                        $scope.clientPage = clientPage;
+                    }
+                );
+            };
+
+            // charts
+            $scope.chartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0
+                },
+                yaxis: {
+                    min: 0,
+                    max: 100,
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fillChartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0,
+                    lines: {
+                        show: true,
+                        fill: true
+                    }
+                },
+                yaxis: {
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fromDate = new Date();
+            $scope.toDate = new Date();
+
+            $scope.fromIsOpen = false;
+            $scope.toIsOpen = false;
+
+            $scope.quickSelectionList = [
+                { id: 0, name: 'Custom' },
+                { id: 1, name: 'Today' },
+                { id: 2, name: 'Yesterday' },
+                { id: 3, name: 'Last 7 Days' },
+                { id: 4, name: 'Last 30 Days' },
+                { id: 5, name: 'Last 6 Months' },
+                { id: 6, name: 'Last Year' }
+            ];
+            $scope.changeQuickSelection = function (quickSelection) {
+                var today = new Date();
+                var oneDayInMs = 24 * 60 * 60 * 1000;
+                switch (quickSelection.id) {
+                    case 1:
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 2:
+                        $scope.fromDate = new Date(today.valueOf() - oneDayInMs);
+                        $scope.toDate = new Date(today.valueOf() - oneDayInMs);
+                        break;
+                    case 3:
+                        $scope.fromDate = new Date(today.valueOf() - (7 * oneDayInMs));
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 4:
+                        $scope.fromDate = new Date(today.valueOf() - (30 * oneDayInMs));
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 5:
+                        var month = today.getMonth() - 6;
+                        if (month < 0) {
+                            month += 12;
+                        }
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setMonth(month);
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 6:
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setFullYear(today.getFullYear() - 1);
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                }
+                $scope.curQuickSelection = quickSelection;
+            };
+            // set default 'today'
+            $scope.changeQuickSelection($scope.quickSelectionList[1]);
+
+            $scope.openFromDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.toIsOpen = false;
+                $scope.fromIsOpen = true;
+                $scope.curQuickSelection = $scope.quickSelectionList[0];
+            };
+
+            $scope.openToDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.fromIsOpen = false;
+                $scope.toIsOpen = true;
+                $scope.curQuickSelection = $scope.quickSelectionList[0];
+            };
+
+            $scope.dateOptions = {
+                formatYear: 'yy',
+                startingDay: 1
+            };
+
+            $scope.cpuSeries = [[]];
+            $scope.coreSeries = [[]];
+            $scope.memorySeries = [[]];
+
+            $scope.updateCharts = function () {
+                clientService.getClientHistoryByGroupId({ id: $stateParams.id, start: ConvertFromDate($scope.fromDate), end: ConvertToDate($scope.toDate) }, function (status) {
+                    var noOfStatus = status.length;
+                    var cpuSeries = [];
+                    var coreSeries = [[], []];
+                    var memorySeries = [[], []];
+                    for (var i = 0; i < noOfStatus; ++i) {
+                        var curStatus = status[i];
+                        var cpuData = Math.round(curStatus.CpuUtilization);
+                        cpuSeries.push([curStatus.Timestamp, cpuData]);
+                        coreSeries[0].push([curStatus.Timestamp, curStatus.TotalCores]);
+                        coreSeries[1].push([curStatus.Timestamp, curStatus.UsedCores]);
+                        memorySeries[0].push([curStatus.Timestamp, curStatus.TotalMemory]);
+                        memorySeries[1].push([curStatus.Timestamp, curStatus.UsedMemory]);
+                    }
+                    $scope.cpuSeries = [{ data: cpuSeries, label: "&nbsp;CPU Utilization", color: "#f7921d" }];
+                    $scope.coreSeries = [
+                        { data: coreSeries[0], label: "&nbsp;Total Cores", color: "LightGreen" },
+                        { data: coreSeries[1], label: "&nbsp;Used Cores", color: "LightPink" }
+                    ];
+                    $scope.memorySeries = [
+                        { data: memorySeries[0], label: "&nbsp;Total Memory", color: "LightGreen" },
+                        { data: memorySeries[1], label: "&nbsp;Used Memory", color: "LightPink" }
+                    ];
+
+                });
+            };
+
+            var update = function () {
+                getGroupDetails();
+                getClients();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+            $scope.updateCharts();
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groups.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groups.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groups.cshtml	(revision 12878)
@@ -0,0 +1,70 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li> 
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Groups</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Group Name</th>
+                            <th>Cores</th>
+                            <th>Cpu Utilization</th>
+                            <th>Memory</th>
+                            <th>Clients</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tr ng-repeat="group in groupPage.Groups">
+                            <td>{{($index + 1)+((curGroupsPage-1)*(groupsPageSize))}}</td>
+                            <td>{{group.Name}}</td>
+                            <td>{{group.UsedCores}} / {{group.TotalCores}}</td>
+                            <td>{{group.CpuUtilization | number: 2}} %</td>
+                            <td>{{group.UsedMemory | kbToGB}} / {{group.TotalMemory | kbToGB}} GB</td>
+                            <td>{{group.OnlineClients}} / {{group.TotalClients}}</td>
+                            <td>
+                                <a ng-href="#/statistics/groups/{{group.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="groupPage.Groups.length">
+                            <td colspan="7" class="text-center">No groups found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="groupPage.TotalGroups > groupPage.Groups.length">
+                        <pagination max-size="10" total-items="groupPage.TotalGroups" ng-model="curGroupsPage" ng-change="changeGroupsPage()" items-per-page="groupsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groupsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groupsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/groups/groupsCtrl.js	(revision 12878)
@@ -0,0 +1,34 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.groupsCtrl',
+        ['$scope', '$interval', 'app.statistics.groupService',
+        function ($scope, $interval, groupService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curGroupsPage = 1;
+            $scope.groupsPageSize = 20;
+
+            var getGroups = function () {
+                groupService.getGroups({ page: $scope.curGroupsPage, size: $scope.groupsPageSize },
+                    function (groupPage) {
+                        $scope.groupPage = groupPage;
+                    }
+                );
+            };
+
+            var update = function () {
+                getGroups();
+            };
+
+            $scope.changeGroupsPage = function () {
+                update();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml	(revision 12878)
@@ -0,0 +1,198 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li class="active">
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-9">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Job Details</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="row">
+                        <div class="col-md-12">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Name:</td>
+                                    <td>{{job.Name}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Created at:</td>
+                                    <td>{{job.DateCreated | toDate}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Completed at:</td>
+                                    <td ng-show="job.DateCompleted">{{job.DateCompleted | toDate}}</td>
+                                    <td ng-hide="job.DateCompleted">Not completed yet</td>
+                                </tr>
+                                @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                                    <tr>
+                                        <td class="text-left">User:</td>
+                                        <td>
+                                            <a ng-href="#/statistics/users/{{job.UserId}}" ng-show="job.UserName">{{job.UserName}}</a>
+                                            <span ng-hide="job.UserName">None</span>
+                                        </td>
+                                    </tr>
+                                }
+                            </table>
+                        </div>
+                    </div>
+                    <div class="row" style="height: 20px"></div>
+                    <div class="row">
+                        <div class="col-md-5">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Total Time:</td>
+                                    <td class="text-right">{{job.TotalTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Calculating Time:</td>
+                                    <td class="text-right">{{job.TotalCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Waiting Time:</td>
+                                    <td class="text-right">{{job.TotalWaitingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Calc. / Waiting Ratio:</td>
+                                    <td class="text-right">{{job.CalculatingWaitingRatio | number: 3}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        <div class="col-md-5">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Min Calculating Time:</td>
+                                    <td class="text-right">{{job.MinCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Max Calculating Time:</td>
+                                    <td class="text-right">{{job.MaxCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Avg. Calculating Time:</td>
+                                    <td class="text-right">{{job.AvgCalculatingTime | toTimespan}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Avg. Transferring Time:</td>
+                                    <td class="text-right">{{job.AvgTransferringTime | toTimespan}}</td>
+                                </tr>
+                            </table>
+                        </div>
+
+                        <div class="col-md-2"></div>
+                        </div>
+                    </div>
+
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-3">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalJobTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in states">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="job-filter-header" class="text-center">
+    <form class="form-inline">
+        <div class="form-group">
+            <div class="btn-group" data-toggle="buttons">
+                <label class="btn btn-default" ng-class="{active: state.Selected}" ng-repeat="state in states"
+                       ng-click="filterState(state)">
+                    <input type="checkbox" autocomplete="off" checked> {{state.State}}
+                </label>
+            </div>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div id="taskListContainer" class="row" ng-show="taskPage.Tasks.length">
+        <div class="col-lg-4 col-md-4" ng-repeat="task in taskPage.Tasks">
+            <div class="panel panel-default">
+                <div class="panel-heading cursor-pointer" ng-click="openDialog(($index + 1)+((curTaskPage-1)*(taskPageSize)), task)">
+                    <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                    <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-condensed table-no-border table-auto-width">
+                        <tr>
+                            <td colspan="2"><span data-fittext="0.99">{{task.Id}}</span></td>
+                        </tr>
+                        <tr>
+                            <td>Start Time:</td>
+                            <td ng-show="task.StartTime" class="text-right">{{task.StartTime | toDate}}</td>
+                            <td ng-hide="task.StartTime" class="text-right">Not started</td>
+                        </tr>
+                        <tr>
+                            <td>End Time:</td>
+                            <td ng-show="task.EndTime" class="text-right">{{task.EndTime | toDate}}</td>
+                            <td ng-hide="task.EndTime" class="text-right">Not finished</td>
+                        </tr>
+                        <tr>
+                            <td>Calculating Time:</td>
+                            <td class="text-right">{{task.CalculatingTime | toTimespan}}</td>
+                        </tr>
+                        <tr>
+                            <td>Slave:</td>
+                            <td class="text-right">
+                                <a ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                <span ng-hide="task.LastClientName">None</span>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+
+            </div>
+        </div>
+    </div>
+    <div ng-hide="taskPage.Tasks.length">
+        <p class="text-center">Please select at least one state!</p>
+    </div>
+    <div class="row text-center" ng-show="taskPage.TotalTasks > taskPage.Tasks.length">
+        <pagination max-size="10" total-items="taskPage.TotalTasks" ng-model="curTaskPage" ng-change="changeTaskPage()" items-per-page="taskPageSize"
+                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js	(revision 12878)
@@ -0,0 +1,102 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.jobDetailsCtrl',
+        ['$scope', '$interval', '$stateParams', 'app.statistics.jobService', 'app.statistics.taskService', '$modal',
+        function ($scope, $interval, $stateParams, jobService, taskService, $modal) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curTaskPage = 1;
+            $scope.taskPageSize = 12;
+
+            var getJobDetails = function () {
+                jobService.getJobDetails({ id: $stateParams.id }, function (job) {
+                    $scope.job = job;
+                    $scope.job.CalculatingWaitingRatio = ($scope.job.TotalCalculatingTime / $scope.job.TotalWaitingTime);
+
+                    var length = job.TasksStates.length;
+                    var total = 0;
+                    var jsStates = [];
+                    for (var i = 0; i < length; ++i) {
+                        var state = job.TasksStates[i];
+                        var selected = true;
+                        if (isDefined($scope.states)) {
+                            for (var j = 0; j < $scope.states.length; ++j) {
+                                if (state.State == $scope.states[j].State) {
+                                    selected = $scope.states[j].Selected;
+                                    break;
+                                }
+                            }
+                        }
+                        jsStates.push({
+                            State: state.State,
+                            Count: state.Count,
+                            Selected: selected
+                        });
+                        total += state.Count;
+                    }
+                    $scope.totalJobTasks = total;
+                    $scope.states = jsStates;
+                    getTasks();
+                });
+            };
+
+            var getTasks = function () {
+                var states = [];
+                var length = $scope.states.length;
+                for (var i = 0; i < length; ++i) {
+                    var state = $scope.states[i];
+                    if (state.Selected) {
+                        states.push(state.State);
+                    }
+                }
+
+                taskService.getTasksByJobId({ id: $stateParams.id, page: $scope.curTaskPage, size: $scope.taskPageSize }, states,
+                    function (taskPage) {
+                        $scope.taskPage = taskPage;
+                    }
+                );
+            };
+
+            $scope.changeTaskPage = function () {
+                getJobDetails();
+                $('html, body').animate({
+                    scrollTop: $("#job-filter-header").offset().top
+                }, 10);
+            };
+
+            $scope.filterState = function (state) {
+                state.Selected = !state.Selected;
+                $scope.curTaskPage = 1;
+                getJobDetails();
+            };
+
+            $scope.openDialog = function (taskNo, task) {
+                $scope.currentTaskNo = taskNo;
+                $scope.currentTask = task;
+                $modal.open({
+                    templateUrl: 'plugin=statistics&view=WebApp/jobs/details/jobTaskDetailsDialog.cshtml',
+                    controller: 'app.statistics.jobTaskDetailsDialogCtrl',
+                    windowClass: 'app-modal-window',
+                    resolve: {
+                        task: function() {
+                            return $scope.currentTask;
+                        },
+                        taskNo: function() {
+                            return $scope.currentTaskNo;
+                        }
+                    }
+                });
+            };
+
+            var update = function () {
+                getJobDetails();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml	(revision 12878)
@@ -0,0 +1,103 @@
+﻿<div class="modal-header">
+    <button type="button" ng-click="close()" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    <h4 class="modal-title">Task #{{taskNo}} - {{task.Id}}</h4>
+</div>
+<div class="modal-body" style="height: 440px">
+    <tabset>
+        <tab heading="Details">
+            <div style="height: 340px" class="center-block">
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-12">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Start Time:</td>
+                                <td ng-show="task.StartTime" class="text-left">{{task.StartTime | toDate}}</td>
+                                <td ng-hide="task.StartTime" class="text-left">Not started</td>
+                            </tr>
+                            <tr>
+                                <td>End Time:</td>
+                                <td ng-show="task.EndTime" class="text-left">{{task.EndTime | toDate}}</td>
+                                <td ng-hide="task.EndTime" class="text-left">Not finished</td>
+                            </tr>
+                            <tr>
+                                <td>State:</td>
+                                <td>{{task.State}}</td>
+                            </tr>
+                            <tr>
+                                <td>Slave:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                    <span ng-hide="task.LastClientName">None</span>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Total Time:</td>
+                                <td>{{task.TotalTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Initial Waiting Time:</td>
+                                <td>{{task.InitialWaitingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Waiting Time:</td>
+                                <td>{{task.WaitingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Calculating Time:</td>
+                                <td>{{task.CalculatingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td>Transfer Time:</td>
+                                <td>{{task.TransferTime | toTimespan}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Calculation Runs:</td>
+                                <td class="text-right">{{task.NumCalculationRuns}}</td>
+                            </tr>
+                            <tr>
+                                <td>Retries Runs:</td>
+                                <td class="text-right">{{task.NumRetries}}</td>
+                            </tr>
+                            <tr>
+                                <td>Cores Required:</td>
+                                <td class="text-right">{{task.CoresRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Memory Required:</td>
+                                <td class="text-right">{{task.MemoryRequired}} MB</td>
+                            </tr>
+                            <tr>
+                                <td>Priority:</td>
+                                <td class="text-right">{{task.Priority}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="col-md-12">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td><a href="api/Statistics/Task/GetTaskDataById?id={{task.Id}}">Download</a></td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </tab>
+        <tab heading="Exception" ng-show="task.Exception">
+            <div style="overflow:auto;">
+                <p style="word-wrap: break-word;">{{task.Exception}}</p>
+            </div>
+        </tab>
+    </tabset>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.jobTaskDetailsDialogCtrl',
+        ['$scope', '$modalInstance', 'taskNo', 'task', function ($scope, $modalInstance, taskNo, task) {
+            $scope.taskNo = taskNo;
+            $scope.task = task;
+            $scope.close = function () {
+                $modalInstance.dismiss('cancel');
+            };
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml	(revision 12878)
@@ -0,0 +1,128 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li class="active">
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/exceptions">Exceptions</a>
+            </li>
+        }
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Current Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Job Name</th>
+                            @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                                <th>Username</th>
+                            }
+                            <th>Date Created</th>
+                            <th>Progress</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                            <tr ng-repeat="job in allUsersJobs">
+                                <td>{{$index + 1}}</td>
+                                <td>{{job.Name}}</td>
+                                <td>
+                                    <a ng-href="#/statistics/users/{{job.UserId}}" ng-show="job.UserName">{{job.UserName}}</a>
+                                </td>
+                                <td>{{job.DateCreated | toDate}}</td>
+                                <td>
+                                    <progressbar class="progress active" max="job.TotalTasks" value="job.CompletedTasks" type="success"><i style="color:black; white-space:nowrap;">{{job.CompletedTasks}} / {{job.TotalTasks}}</i></progressbar>
+                                </td>
+                                <td>
+                                    <a ng-href="#/statistics/jobs/{{job.Id}}">Details</a>
+                                </td>
+                            </tr>
+                            <tr ng-hide="allUsersJobs.length">
+                                <td colspan="6" class="text-center">No active jobs found!</td>
+                            </tr>
+                        } else {
+                            <tr ng-repeat="job in jobs">
+                                <td>{{$index + 1}}</td>
+                                <td>{{job.Name}}</td>
+                                <td>{{job.DateCreated | toDate}}</td>
+                                <td>
+                                    <progressbar class="progress active" max="job.TotalTasks" value="job.CompletedTasks" type="success"><i style="color: black; white-space: nowrap;">{{job.CompletedTasks}} / {{job.TotalTasks}}</i></progressbar>
+                                </td>
+                                <td>
+                                    <a ng-href="#/statistics/jobs/{{job.Id}}">Details</a>
+                                </td>
+                            </tr>
+                            <tr ng-hide="jobs.length">
+                                <td colspan="5" class="text-center">No active jobs found!</td>
+                            </tr>
+                        }
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Completed Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Job Name</th>
+                            <th>Date Created</th>
+                            <th>Date Completed</th>
+                            <th>Tasks</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tr ng-repeat="job in completedJobPage.Jobs">
+                            <td>{{($index + 1)+((completedJobCurPage-1)*(completedJobPageSize))}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated | toDate}}</td>
+                            <td>{{job.DateCompleted | toDate}}</td>
+                            <td>{{job.TotalTasks}}</td>
+                            <td><a ng-href="#/statistics/jobs/{{job.Id}}">Details</a></td>
+                        </tr>
+                        <tr ng-hide="completedJobPage.Jobs.length">
+                            <td colspan="6" class="text-center">No completed jobs found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="completedJobPage.TotalJobs > completedJobPage.Jobs.length">
+                        <pagination max-size="10" total-items="completedJobPage.TotalJobs"
+                                    ng-model="completedJobCurPage" items-per-page="completedJobPageSize"
+                                    ng-change="changeCompletedJobPage()"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js	(revision 12878)
@@ -0,0 +1,53 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.jobsCtrl',
+        ['$scope', '$interval', 'app.statistics.jobService', function ($scope, $interval, jobService) {
+            var first = true;
+            $scope.isAdministrator = false;
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.completedJobCurPage = 1;
+            $scope.completedJobPageSize = 20;
+
+            var getAllJobs = function() {
+                jobService.getAllJobs({ completed: false }, function(jobs) {
+                    $scope.jobs = jobs;
+                });
+            };
+
+            var getCompletedJobs = function() {
+                jobService.getJobs({ page: $scope.completedJobCurPage, size: $scope.completedJobPageSize, completed: true },
+                    function (jobPage) {
+                        $scope.completedJobPage = jobPage;
+                    }
+                );
+            };
+
+            var getAllActiveJobsFromAllUsers = function () {
+                jobService.getAllActiveJobsFromAllUsers({}, function (jobs) {
+                    $scope.isAdministrator = true;
+                    $scope.allUsersJobs = jobs;
+                });
+            };
+
+            $scope.changeCompletedJobPage = function () {
+                update();
+            };
+
+            var update = function () {
+                getAllJobs();
+                if (first || $scope.isAdministrator) {
+                    getAllActiveJobsFromAllUsers();
+                }
+                getCompletedJobs();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+            first = false;
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js	(revision 12878)
@@ -0,0 +1,15 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Client/';
+    module.factory('app.statistics.clientService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', expired: '@expired', start: '@start', end: '@end', userId: '@userId' }, {
+                getClientDetails: { method: 'GET', params: { action: 'GetClientDetails' } },
+                getClients: { method: 'GET', params: { action: 'GetClients' } },
+                getClientsByGroupId: { method: 'GET', params: { action: 'GetClientsByGroupId' } },
+                getClientHistory: { method: 'GET', params: { action: 'GetClientHistory' }, isArray: true },
+                getClientHistoryByGroupId: { method: 'GET', params: { action: 'GetClientHistoryByGroupId' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/exceptionService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/exceptionService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/exceptionService.js	(revision 12878)
@@ -0,0 +1,11 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Exception/';
+    module.factory('app.statistics.exceptionService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { page: '@page', size: '@size'}, {
+                getExceptions: { method: 'GET', params: { action: 'GetExceptions' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/groupService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/groupService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/groupService.js	(revision 12878)
@@ -0,0 +1,13 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Group/';
+    module.factory('app.statistics.groupService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', start: '@start', end: '@end' }, {
+                getGroupDetails: { method: 'GET', params: { action: 'GetGroupDetails' } },
+                getGroups: { method: 'GET', params: { action: 'GetGroups' } },
+                getGroupHistory: { method: 'GET', params: { action: 'GetGroupHistory' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js	(revision 12878)
@@ -0,0 +1,17 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Job/';
+    module.factory('app.statistics.jobService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', completed: '@completed' }, {
+                getJobDetails: { method: 'GET', params: { action: 'GetJobDetails' } },
+                getJobs: { method: 'GET', params: { action: 'GetJobs' } },
+                getAllJobs: { method: 'GET', params: { action: 'GetAllJobs'}, isArray: true },
+                getJobsByUserId: { method: 'GET', params: { action: 'GetJobsByUserId' } },
+                getAllJobsByUserId: { method: 'GET', params: { action: 'GetAllJobsByUserId'}, isArray: true },
+                getJobTasksStatesByJobId: { method: 'GET', params: { action: 'GetJobTasksStatesByJobId' }, isArray: true },
+                getAllActiveJobsFromAllUsers: { method: 'GET', params: { action: 'GetAllActiveJobsFromAllUsers' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js	(revision 12878)
@@ -0,0 +1,29 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Data/';
+    module.factory('app.statistics.data.service',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', expired: '@expired'}, {
+                getJob: { method: 'GET', params: { action: 'GetJob' } },
+                getJobs: { method: 'GET', params: { action: 'GetJobs' }, isArray: true },
+                getCompletedJobs: { method: 'GET', params: { action: 'GetCompletedJobs' }, isArray: true },
+                getCompletedJobsByUserId: { method: 'GET', params: { action: 'GetCompletedJobsByUserId' }, isArray: true },
+                getCompletedJobsCount: { method: 'GET', params: { action: 'GetCompletedJobsCount' } },
+                getCompletedJobsCountByUserId: { method: 'GET', params: { action: 'GetCompletedJobsCountByUserId' } },
+                getJobsByUserId: { method: 'GET', params: { action: 'GetJobsByUserId' }, isArray: true },
+                getTaskCountByJobId: { method: 'GET', params: { action: 'GetTaskCountByJobId' } },
+                getTasksByJobId: { method: 'GET', params: { action: 'GetTasksByJobId' }, isArray: true },
+                getJobTasksStatesByJobId: { method: 'GET', params: { action: 'GetJobTasksStatesByJobId' }, isArray: true },
+                getClient: { method: 'GET', params: { action: 'GetClient' } },
+                getClientCount: { method: 'GET', params: { action: 'GetClientCount' } },
+                getClients: { method: 'GET', params: { action: 'GetClients' }, isArray: true },
+                getTaskCountByClientId: { method: 'GET', params: { action: 'GetTaskCountByClientId' } },
+                getTasksByClientId: { method: 'GET', params: { action: 'GetTasksByClientId' }, isArray: true },
+                getJobTasksStatesByClientId: { method: 'GET', params: { action: 'GetJobTasksStatesByClientId' }, isArray: true },
+                getClientHistory: { method: 'GET', params: { action: 'GetClientHistory' }, isArray: true },
+                getUsers: { method: 'GET', params: { action: 'GetUsers' }, isArray: true },
+                getUser: { method: 'GET', params: { action: 'GetUser' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Task/';
+    module.factory('app.statistics.taskService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states' }, {
+                getTasksByJobId: { method: 'POST', params: { action: 'GetTasksByJobId' } },
+                getTasksByClientId: { method: 'POST', params: { action: 'GetTasksByClientId' } }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js	(revision 12878)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/User/';
+    module.factory('app.statistics.userService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id' }, {
+                getUser: { method: 'GET', params: { action: 'GetUser' } },
+                getUsers: { method: 'GET', params: { action: 'GetUsers' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css	(revision 12878)
@@ -0,0 +1,6 @@
+﻿#job-filter-header {
+  padding: 30px;
+  background: #F5F5F5;
+  border-top: 1px solid #D3D3D3;
+  border-bottom: 1px solid #D3D3D3;
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml	(revision 12878)
@@ -0,0 +1,141 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/users">Users</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/exceptions">Exceptions</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">User Details</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Name:</td>
+                            <td>{{user.Name}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalUserTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in user.TasksStates">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Current Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Job Name</th>
+                                <th>Date Created</th>
+                                <th>Progress</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="job in jobs">
+                            <td>{{$index + 1}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated | toDate}}</td>
+                            <td>
+                                <progressbar class="progress active" max="job.TotalTasks" value="job.CompletedTasks" type="success"><i style="color:black; white-space:nowrap;">{{job.CompletedTasks}} / {{job.TotalTasks}}</i></progressbar>
+                            </td>
+                            <td>
+                                <a ng-href="#/statistics/jobs/{{job.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="jobs.length">
+                            <td colspan="5" class="text-center">No active jobs found!</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Completed Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Job Name</th>
+                                <th>Date Created</th>
+                                <th>Date Completed</th>
+                                <th>Tasks</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="job in completedJobPage.Jobs">
+                            <td>{{($index + 1)+((completedJobCurPage-1)*(completedJobPageSize))}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated | toDate}}</td>
+                            <td>{{job.DateCompleted | toDate}}</td>
+                            <td>{{job.TotalTasks}}</td>
+                            <td><a ng-href="#/statistics/jobs/{{job.Id}}">Details</a></td>
+                        </tr>
+                        <tr ng-hide="completedJobPage.Jobs.length">
+                            <td colspan="6" class="text-center">No completed jobs found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="completedJobPage.TotalJobs > completedJobPage.Jobs.length">
+                        <pagination max-size="10" total-items="completedJobPage.TotalJobs"
+                                    ng-model="completedJobCurPage" items-per-page="completedJobPageSize"
+                                    ng-change="changeCompletedJobPage()"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js	(revision 12878)
@@ -0,0 +1,55 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.userDetailsCtrl',
+        ['$scope', '$stateParams', '$interval', 'app.statistics.userService', 'app.statistics.jobService',
+        function ($scope, $stateParams, $interval, userService, jobService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.completedJobCurPage = 1;
+            $scope.completedJobPageSize = 20;
+
+            var getUserDetails = function () {
+                userService.getUser({ id: $stateParams.id }, function(user) {
+                    $scope.user = user;
+
+                    var length = user.TasksStates.length;
+                    var total = 0;
+                    for (var i = 0; i < length; ++i) {
+                        total += user.TasksStates[i].Count;
+                    }
+                    $scope.totalUserTasks = total;
+                });
+            };
+
+            var getAllJobs = function () {
+                jobService.getAllJobsByUserId({ id: $stateParams.id, completed: false }, function (jobs) {
+                    $scope.jobs = jobs;
+                });
+            };
+
+            var getCompletedJobs = function () {
+                jobService.getJobsByUserId({ id: $stateParams.id, page: $scope.completedJobCurPage, size: $scope.completedJobPageSize, completed: true },
+                    function (jobPage) {
+                        $scope.completedJobPage = jobPage;
+                    }
+                );
+            };
+
+            $scope.changeCompletedJobPage = function () {
+                update();
+            };
+
+            var update = function () {
+                getUserDetails();
+                getAllJobs();
+                getCompletedJobs();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml	(revision 12878)
@@ -0,0 +1,56 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/users">Users</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/groups">Groups</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/exceptions">Exceptions</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Users</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Username</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="user in users">
+                            <td>{{$index + 1}}</td>
+                            <td>{{user.Name}}</td>
+                            <td>
+                                <a ng-href="#/statistics/users/{{user.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="users.length">
+                            <td colspan="4" class="text-center">No users found!</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js	(revision 12878)
@@ -0,0 +1,22 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.usersCtrl',
+        ['$scope', '$interval', 'app.statistics.userService', function ($scope, $interval, userService) {
+            $scope.interval = defaultPageUpdateInterval;
+
+            var getUsers = function() {
+                userService.getUsers({}, function(users) {
+                    users.splice(0, 1);
+                    $scope.users = users;
+                });
+            };
+
+            $scope.updateInterval = $interval(getUsers, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            getUsers(); // init page
+        }]
+    );
+})();
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/packages.config
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/packages.config	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/packages.config	(revision 12878)
@@ -0,0 +1,12 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
+  <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" />
+  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" />
+</packages>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js	(revision 12878)
@@ -0,0 +1,49 @@
+﻿var appStatisticsPlugin = app.registerPlugin('statistics');
+(function () {
+    var plugin = appStatisticsPlugin;
+    plugin.dependencies = ['ngResource', 'ui.knob', 'ui.bootstrap', 'ngFitText'];
+    plugin.files = [
+        'WebApp/statistics.css',
+        'WebApp/services/statisticsService.js',
+        'WebApp/services/clientService.js',
+        'WebApp/services/exceptionService.js',
+        'WebApp/services/groupService.js',
+        'WebApp/services/jobService.js',
+        'WebApp/services/taskService.js',
+        'WebApp/services/userService.js',
+        'WebApp/jobs/jobsCtrl.js',
+        'WebApp/jobs/details/jobDetailsCtrl.js',
+        'WebApp/jobs/details/jobTaskDetailsDialogCtrl.js',
+        'WebApp/clients/clientsCtrl.js',
+        'WebApp/clients/details/clientDetailsCtrl.js',
+        'WebApp/clients/details/clientTaskDetailsDialogCtrl.js',
+        'WebApp/users/usersCtrl.js',
+        'WebApp/users/details/userDetailsCtrl.js',
+        'WebApp/groups/groupsCtrl.js',
+        'WebApp/groups/details/groupDetailsCtrl.js',
+        'WebApp/exceptions/exceptionsCtrl.js',
+        'WebApp/exceptions/exceptionDetailsDialogCtrl.js'
+    ];
+    plugin.view = 'WebApp/jobs/jobs.cshtml';
+    plugin.controller = 'app.statistics.jobsCtrl';
+    plugin.routes = [
+        new Route('jobs', 'WebApp/jobs/jobs.cshtml', 'app.statistics.jobsCtrl'),
+        new Route('jobs/:id', 'WebApp/jobs/details/jobDetails.cshtml', 'app.statistics.jobDetailsCtrl'),
+        new Route('clients', 'WebApp/clients/clients.cshtml', 'app.statistics.clientsCtrl'),
+        new Route('clients/:id', 'WebApp/clients/details/clientDetails.cshtml', 'app.statistics.clientDetailsCtrl'),
+        new Route('users', 'WebApp/users/users.cshtml', 'app.statistics.usersCtrl'),
+        new Route('users/:id', 'WebApp/users/details/userDetails.cshtml', 'app.statistics.userDetailsCtrl'),
+        new Route('groups', 'WebApp/groups/groups.cshtml', 'app.statistics.groupsCtrl'),
+        new Route('groups/:id', 'WebApp/groups/details/groupDetails.cshtml', 'app.statistics.groupDetailsCtrl'),
+        new Route('exceptions', 'WebApp/exceptions/exceptions.cshtml', 'app.statistics.exceptionsCtrl')
+    ];
+    var menu = app.getMenu();
+    var section = menu.getSection('Menu', 1);
+    section.addEntry({
+        name: 'Statistics',
+        route: '#/statistics',
+        icon: 'glyphicon glyphicon-stats',
+        entries: []
+    });
+})();
+
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/HeuristicLab.Services.WebApp.Status-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/HeuristicLab.Services.WebApp.Status-3.3.csproj	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/HeuristicLab.Services.WebApp.Status-3.3.csproj	(revision 12878)
@@ -9,5 +9,5 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>HeuristicLab.Services.WebApp.Status</RootNamespace>
-    <AssemblyName>HeuristicLab.Services.WebApp.Status</AssemblyName>
+    <AssemblyName>HeuristicLab.Services.WebApp.Status-3.3</AssemblyName>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
@@ -76,4 +76,5 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>False</Private>
     </Reference>
     <Reference Include="System" />
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataController.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataController.cs	(revision 12878)
@@ -22,16 +22,19 @@
 using System;
 using System.Collections.Generic;
-using System.Data.Linq;
 using System.Linq;
 using System.Web.Http;
 using HeuristicLab.Services.Hive;
 using HeuristicLab.Services.Hive.DataAccess;
-using DAL = HeuristicLab.Services.Hive.DataAccess;
-using DTO = HeuristicLab.Services.WebApp.Status.WebApi.DataTransfer;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Status.WebApi.DataTransfer;
 
 namespace HeuristicLab.Services.WebApp.Status.WebApi {
   public class DataController : ApiController {
-
-    // start temporary quickfix
+    private const int LAST_TASKS = 20;
+
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
     private const string SQL_USER_TASK_STATUS =
       @"WITH UserTasks AS (
@@ -53,71 +56,98 @@
     }
 
-    public IEnumerable<DTO.TaskStatus> GetTaskStatus(HiveDataContext db) {
-      var query = db.ExecuteQuery<UserTaskStatus>(SQL_USER_TASK_STATUS).ToList();
-      return query.Select(uts => new DTO.TaskStatus {
-        User = new DTO.User {
-          Id = uts.UserId.ToString(),
-          Name = ServiceLocator.Instance.UserManager.GetUserById(uts.UserId).UserName
+    private IEnumerable<DT.TaskStatus> GetTaskStatus(IPersistenceManager pm) {
+      return pm.UseTransaction(() => {
+        var query = pm.DataContext.ExecuteQuery<UserTaskStatus>(SQL_USER_TASK_STATUS).ToList();
+        return query.Select(uts => new DT.TaskStatus {
+          User = new DT.User {
+            Id = uts.UserId.ToString(),
+            Name = ServiceLocator.Instance.UserManager.GetUserById(uts.UserId).UserName
+          },
+          CalculatingTasks = uts.CalculatingTasks,
+          WaitingTasks = uts.WaitingTasks
+        }).OrderBy(x => x.User.Name);
+      });
+    }
+
+    private DT.TimeStatus GetTimeStatus(IPersistenceManager pm) {
+      return pm.UseTransaction(() => {
+        var factTaskDao = pm.FactTaskDao;
+        var factTasks = factTaskDao.GetAll();
+        var completedTasks = factTaskDao.GetCompletedTasks()
+          .OrderByDescending(x => x.EndTime)
+          .Take(LAST_TASKS);
+        var lastCalculatingTimes = completedTasks
+          .GroupBy(x => 1)
+          .Select(x => new {
+            Min = x.Min(y => y.CalculatingTime),
+            Max = x.Max(y => y.CalculatingTime),
+            Avg = (long)x.Average(y => (long?)y.CalculatingTime)
+          }).FirstOrDefault();
+        var calculatingTasks = factTasks.Where(x => x.TaskState == TaskState.Calculating);
+        int count = calculatingTasks.Count() / 3;
+        return new DT.TimeStatus {
+          MinCalculatingTime = lastCalculatingTimes != null ? lastCalculatingTimes.Min : 0,
+          MaxCalculatingTime = lastCalculatingTimes != null ? lastCalculatingTimes.Max : 0,
+          AvgWaitingTime = count > 0 ? (long)calculatingTasks.OrderBy(x => x.StartTime).Take(count).Average(x => x.InitialWaitingTime) : 0,
+          AvgCalculatingTime = lastCalculatingTimes != null ? lastCalculatingTimes.Avg : 0,
+          TotalCpuTime = factTasks.Sum(x => x.CalculatingTime),
+          StandardDeviationCalculatingTime = (long)StandardDeviation(completedTasks.Select(x => (double)x.CalculatingTime)),
+          BeginDate = factTasks.OrderBy(x => x.StartTime).Select(x => x.StartTime).FirstOrDefault()
+        };
+      });
+    }
+
+    public DT.Status GetStatus() {
+      var pm = PersistenceManager;
+      var slaveDao = pm.SlaveDao;
+      var onlineSlaves = pm.UseTransaction(() => slaveDao.GetOnlineSlaves().ToList());
+      var activeSlaves = onlineSlaves.Where(s => s.IsAllowedToCalculate).ToList();
+      var calculatingSlaves = activeSlaves.Where(s => s.SlaveState == SlaveState.Calculating).ToList();
+      int totalCores = onlineSlaves.Sum(s => s.Cores ?? 0);
+      int totalMemory = onlineSlaves.Sum(s => s.Memory ?? 0);
+      return new DT.Status {
+        CoreStatus = new DT.CoreStatus {
+          TotalCores = totalCores,
+          UsedCores = totalCores - onlineSlaves.Sum(s => s.FreeCores ?? 0),
+          ActiveCores = activeSlaves.Sum(s => s.Cores ?? 0),
+          CalculatingCores = calculatingSlaves.Sum(s => s.Cores ?? 0) - calculatingSlaves.Sum(s => s.FreeCores ?? 0)
         },
-        CalculatingTasks = uts.CalculatingTasks,
-        WaitingTasks = uts.WaitingTasks
-      }).OrderBy(x => x.User.Name);
-    }
-    // end temporary quickfix
-
-    public DTO.Status GetStatus() {
-      using (var db = new HiveDataContext()) {
-        var onlineSlaves = (from slave in db.Resources.OfType<DAL.Slave>()
-                            where slave.SlaveState == SlaveState.Calculating || slave.SlaveState == SlaveState.Idle
-                            select slave).ToList();
-        var activeSlaves = onlineSlaves.Where(s => s.IsAllowedToCalculate).ToList();
-        var calculatingSlaves = activeSlaves.Where(s => s.SlaveState == SlaveState.Calculating).ToList();
-        int calculatingMemory = calculatingSlaves.Any() ? (int)calculatingSlaves.Sum(s => s.Memory) : 0;
-        int freeCalculatingMemory = calculatingSlaves.Any() ? (int)calculatingSlaves.Sum(s => s.FreeMemory) : 0;
-
-        return new DTO.Status {
-          CoreStatus = new DTO.CoreStatus {
-            TotalCores = onlineSlaves.Sum(s => s.Cores ?? 0),
-            FreeCores = onlineSlaves.Sum(s => s.FreeCores ?? 0), // temporary for old chart data
-            ActiveCores = activeSlaves.Sum(s => s.Cores ?? 0),
-            CalculatingCores = calculatingSlaves.Sum(s => s.Cores ?? 0) - calculatingSlaves.Sum(s => s.FreeCores ?? 0)
+        CpuUtilizationStatus = new DT.CpuUtilizationStatus {
+          TotalCpuUtilization = onlineSlaves.Any()
+                                ? Math.Round(onlineSlaves.Average(s => s.CpuUtilization), 2)
+                                : 0.0,
+          ActiveCpuUtilization = activeSlaves.Any()
+                                 ? Math.Round(activeSlaves.Average(s => s.CpuUtilization), 2)
+                                 : 0.0,
+          CalculatingCpuUtilization = calculatingSlaves.Any()
+                                      ? Math.Round(calculatingSlaves.Average(s => s.CpuUtilization), 2)
+                                      : 0.0
+        },
+        MemoryStatus = new DT.MemoryStatus {
+          TotalMemory = totalMemory,
+          UsedMemory = totalMemory - onlineSlaves.Sum(s => s.FreeMemory ?? 0),
+          ActiveMemory = activeSlaves.Sum(s => s.Memory ?? 0),
+          CalculatingMemory = calculatingSlaves.Sum(s => s.Memory ?? 0) - calculatingSlaves.Sum(s => s.FreeMemory ?? 0)
+        },
+        TimeStatus = GetTimeStatus(pm),
+        TasksStatus = GetTaskStatus(pm),
+        SlavesStatus = onlineSlaves.Select(x => new DT.SlaveStatus {
+          Slave = new DT.Slave {
+            Id = x.ResourceId.ToString(),
+            Name = x.Name
           },
-          CpuUtilizationStatus = new DTO.CpuUtilizationStatus {
-            TotalCpuUtilization = onlineSlaves.Any()
-                                  ? Math.Round(onlineSlaves.Average(s => s.CpuUtilization), 2)
-                                  : 0.0,
-            ActiveCpuUtilization = activeSlaves.Any()
-                                   ? Math.Round(activeSlaves.Average(s => s.CpuUtilization), 2)
-                                   : 0.0,
-            CalculatingCpuUtilization = calculatingSlaves.Any()
-                                        ? Math.Round(calculatingSlaves.Average(s => s.CpuUtilization), 2)
-                                        : 0.0
-          },
-          MemoryStatus = new DTO.MemoryStatus {
-            TotalMemory = onlineSlaves.Any() ? (int)onlineSlaves.Sum(s => s.Memory) : 0,
-            FreeMemory = onlineSlaves.Any() ? (int)onlineSlaves.Sum(s => s.FreeMemory) : 0,
-            ActiveMemory = activeSlaves.Any() ? (int)activeSlaves.Sum(s => s.Memory) : 0,
-            UsedMemory = calculatingMemory - freeCalculatingMemory
-          },
-          TasksStatus = GetTaskStatus(db),
-          SlavesStatus = onlineSlaves.Select(x => new DTO.SlaveStatus {
-            Slave = new DTO.Slave {
-              Id = x.ResourceId.ToString(),
-              Name = x.Name
-            },
-            CpuUtilization = x.CpuUtilization,
-            Cores = x.Cores ?? 0,
-            FreeCores = x.FreeCores ?? 0,
-            Memory = x.Memory ?? 0,
-            FreeMemory = x.FreeMemory ?? 0,
-            IsAllowedToCalculate = x.IsAllowedToCalculate,
-            State = x.SlaveState.ToString()
-          }).OrderBy(x => x.Slave.Name),
-          Timestamp = JavascriptUtils.ToTimestamp(DateTime.Now)
-        };
-      }
-    }
-
-    public IEnumerable<DTO.Status> GetStatusHistory(DateTime start, DateTime end) {
+          CpuUtilization = x.CpuUtilization,
+          Cores = x.Cores ?? 0,
+          FreeCores = x.FreeCores ?? 0,
+          Memory = x.Memory ?? 0,
+          FreeMemory = x.FreeMemory ?? 0,
+          IsAllowedToCalculate = x.IsAllowedToCalculate,
+          State = x.SlaveState.ToString()
+        }).OrderBy(x => x.Slave.Name),
+        Timestamp = JavascriptUtils.ToTimestamp(DateTime.Now)
+      };
+    }
+
+    public IEnumerable<DT.Status> GetStatusHistory(DateTime start, DateTime end) {
       TimeSpan ts = end - start;
       int increment = 1;
@@ -127,55 +157,71 @@
         increment += 5;
       }
-      using (var db = new HiveDataContext()) {
-        DataLoadOptions loadOptions = new DataLoadOptions();
-        loadOptions.LoadWith<Statistics>(o => o.SlaveStatistics);
-        db.LoadOptions = loadOptions;
-        db.DeferredLoadingEnabled = false;
-        var statistics = db.Statistics.Where(s => s.Timestamp >= start && s.Timestamp <= end)
-                                      .OrderBy(s => s.Timestamp)
-                                      .ToList();
-        var status = new DTO.Status {
-          CoreStatus = new DTO.CoreStatus(),
-          CpuUtilizationStatus = new DTO.CpuUtilizationStatus(),
-          MemoryStatus = new DTO.MemoryStatus()
+      var pm = PersistenceManager;
+      var factClientInfoDao = pm.FactClientInfoDao;
+      var clientInfos = pm.UseTransaction(() => {
+        return factClientInfoDao.GetAll()
+          .Where(s => s.Time >= start
+                      && s.Time <= end
+                      && s.SlaveState != SlaveState.Offline)
+          .OrderBy(s => s.Time)
+          .GroupBy(s => s.Time)
+          .Select(x => new {
+            Timestamp = x.Key,
+            TotalCores = x.Sum(y => y.NumTotalCores),
+            UsedCores = x.Sum(y => y.NumUsedCores),
+            TotalMemory = x.Sum(y => y.TotalMemory),
+            UsedMemory = x.Sum(y => y.UsedMemory),
+            CpuUtilization = x.Where(y => y.IsAllowedToCalculate).Average(y => y.CpuUtilization)
+          })
+          .ToList();
+      });
+      var statusList = new List<DT.Status>();
+      var e = clientInfos.GetEnumerator();
+      do {
+        var status = new DT.Status {
+          CoreStatus = new DT.CoreStatus(),
+          CpuUtilizationStatus = new DT.CpuUtilizationStatus(),
+          MemoryStatus = new DT.MemoryStatus()
         };
-        int freeCores = 0;
-        int freeMemory = 0;
-        int i = 1;
-        foreach (var statistic in statistics) {
-          status.CoreStatus.TotalCores += statistic.SlaveStatistics.Sum(x => x.Cores);
-          freeCores += statistic.SlaveStatistics.Sum(x => x.FreeCores);
-          status.CpuUtilizationStatus.TotalCpuUtilization += statistic.SlaveStatistics.Any()
-                                                             ? statistic.SlaveStatistics.Average(x => x.CpuUtilization)
-                                                             : 0.0;
-          status.MemoryStatus.TotalMemory += statistic.SlaveStatistics.Sum(x => x.Memory);
-          freeMemory += statistic.SlaveStatistics.Sum(x => x.FreeMemory);
-          if (i >= increment) {
-            status.Timestamp = JavascriptUtils.ToTimestamp(statistic.Timestamp);
-            status.CoreStatus.TotalCores /= i;
-            freeCores /= i;
-            status.CpuUtilizationStatus.TotalCpuUtilization /= i;
-            status.MemoryStatus.TotalMemory /= i;
-            freeMemory /= i;
-            status.CoreStatus.ActiveCores = status.CoreStatus.TotalCores;
-            status.MemoryStatus.ActiveMemory = status.MemoryStatus.TotalMemory;
-            status.CpuUtilizationStatus.ActiveCpuUtilization = status.CpuUtilizationStatus.TotalCpuUtilization;
-            status.CpuUtilizationStatus.CalculatingCpuUtilization = status.CpuUtilizationStatus.CalculatingCpuUtilization;
-            status.CoreStatus.CalculatingCores = status.CoreStatus.TotalCores - freeCores;
-            status.MemoryStatus.UsedMemory = status.MemoryStatus.TotalMemory - freeMemory;
-            yield return status;
-            status = new DTO.Status {
-              CoreStatus = new DTO.CoreStatus(),
-              CpuUtilizationStatus = new DTO.CpuUtilizationStatus(),
-              MemoryStatus = new DTO.MemoryStatus()
-            };
-            freeCores = 0;
-            freeMemory = 0;
-            i = 1;
-          } else {
-            i++;
-          }
+        int i = 0;
+        DateTime lastTimestamp = DateTime.Now;
+        while (e.MoveNext()) {
+          var clientInfo = e.Current;
+          status.CoreStatus.TotalCores += clientInfo.TotalCores;
+          status.CoreStatus.UsedCores += clientInfo.UsedCores;
+          status.MemoryStatus.TotalMemory += clientInfo.TotalMemory;
+          status.MemoryStatus.UsedMemory += clientInfo.UsedMemory;
+          status.CpuUtilizationStatus.TotalCpuUtilization += clientInfo.CpuUtilization;
+          lastTimestamp = clientInfo.Timestamp;
+          i++;
+          if (i >= increment)
+            break;
         }
+        if (i <= 0) continue;
+        status.Timestamp = JavascriptUtils.ToTimestamp(lastTimestamp);
+        status.CoreStatus.TotalCores /= i;
+        status.CoreStatus.UsedCores /= i;
+        status.MemoryStatus.TotalMemory /= i;
+        status.MemoryStatus.UsedMemory /= i;
+        status.CpuUtilizationStatus.TotalCpuUtilization /= i;
+        statusList.Add(status);
+      } while (e.Current != null);
+      return statusList;
+    }
+
+    private double StandardDeviation(IEnumerable<double> source) {
+      int n = 0;
+      double mean = 0;
+      double M2 = 0;
+      foreach (double x in source) {
+        n = n + 1;
+        double delta = x - mean;
+        mean = mean + delta / n;
+        M2 += delta * (x - mean);
       }
+      if (n < 2) {
+        return M2;
+      }
+      return Math.Sqrt(M2 / (n - 1));
     }
   }
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs	(revision 12878)
@@ -20,4 +20,5 @@
 #endregion
 
+using System;
 using System.Collections.Generic;
 
@@ -26,5 +27,5 @@
   public class CoreStatus {
     public int TotalCores { get; set; }
-    public int FreeCores { get; set; } // temporary quickfix for old chart data
+    public int UsedCores { get; set; }
     public int ActiveCores { get; set; }
     public int CalculatingCores { get; set; }
@@ -39,7 +40,7 @@
   public class MemoryStatus {
     public int TotalMemory { get; set; }
-    public int FreeMemory { get; set; } // temporary quickfix for old chart data
+    public int UsedMemory { get; set; }
     public int ActiveMemory { get; set; }
-    public int UsedMemory { get; set; }
+    public int CalculatingMemory { get; set; }
   }
 
@@ -61,8 +62,19 @@
   }
 
+  public class TimeStatus {
+    public long MinCalculatingTime { get; set; }
+    public long MaxCalculatingTime { get; set; }
+    public long AvgCalculatingTime { get; set; }
+    public long StandardDeviationCalculatingTime { get; set; }
+    public long AvgWaitingTime { get; set; }
+    public long TotalCpuTime { get; set; }
+    public DateTime? BeginDate { get; set; }
+  }
+
   public class Status {
     public CoreStatus CoreStatus { get; set; }
     public CpuUtilizationStatus CpuUtilizationStatus { get; set; }
     public MemoryStatus MemoryStatus { get; set; }
+    public TimeStatus TimeStatus { get; set; }
     public IEnumerable<TaskStatus> TasksStatus { get; set; }
     public IEnumerable<SlaveStatus> SlavesStatus { get; set; }
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml	(revision 12878)
@@ -15,5 +15,5 @@
             <label for="fromDate">From:&nbsp;</label>
             <div class="input-group">
-                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close"/>
                 <span class="input-group-btn">
                     <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
@@ -32,11 +32,11 @@
         <div class="form-group" style="margin-left: 5px; margin-right: 5px;">
             <div class="btn-group" dropdown dropdown-append-to-body>
-                <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width: 130px; text-align: left;">
-                    <span class="text-left" style="display: inline-block; width: 85px;">{{curQuickSelection.name}}</span>
+                <button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle style="width: 145px; text-align: left;">
+                    <span class="text-left" style="display: inline-block; width: 100px;">{{curQuickSelection.name}}</span>
                     <span class="glyphicon glyphicon-chevron-down" style="margin-top: 3px;"></span>
                 </button>
                 <ul class="dropdown-menu" role="menu">
                     <li ng-repeat="quickSelection in quickSelectionList">
-                        <a ng-click="changeQuickSelection(quickSelection)">{{quickSelection.name}}</a>
+                        <a class="cursor-pointer" ng-click="changeQuickSelection(quickSelection)">{{quickSelection.name}}</a>
                     </li>
                 </ul>
@@ -50,5 +50,4 @@
     </form>
 </div>
-
 
 <div class="default-view-container">
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/historyCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/historyCtrl.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/historyCtrl.js	(revision 12878)
@@ -2,6 +2,6 @@
     var module = appStatusPlugin.getAngularModule();
     module.controller('app.status.historyCtrl',
-        ['$scope', '$interval', 'app.status.data.service',
-        function ($scope, $interval, dataService) {
+        ['$scope', '$interval', 'app.status.data.service', '$log',
+        function ($scope, $interval, dataService, $log) {
             $scope.chartOptions = {
                 grid: {
@@ -40,5 +40,4 @@
             };
 
-
             $scope.fromDate = new Date();
             $scope.toDate = new Date();
@@ -52,5 +51,7 @@
                 { id: 2, name: 'Yesterday' },
                 { id: 3, name: 'Last 7 Days' },
-                { id: 4, name: 'Last 30 Days' }
+                { id: 4, name: 'Last 30 Days' },
+                { id: 5, name: 'Last 6 Months' },
+                { id: 6, name: 'Last Year' }
             ];
             $scope.changeQuickSelection = function (quickSelection) {
@@ -72,4 +73,18 @@
                     case 4:
                         $scope.fromDate = new Date(today.valueOf() - (30 * oneDayInMs));
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 5:
+                        var month = today.getMonth() - 6;
+                        if (month < 0) {
+                            month += 12;
+                        }
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setMonth(month);
+                        $scope.toDate = new Date(today.valueOf());
+                        break;
+                    case 6:
+                        $scope.fromDate = new Date(today.valueOf());
+                        $scope.fromDate.setFullYear(today.getFullYear()-1);
                         $scope.toDate = new Date(today.valueOf());
                         break;
@@ -113,9 +128,9 @@
                     for (var i = 0; i < noOfStatus; ++i) {
                         var curStatus = status[i];
-                        var cpuData = Math.round(curStatus.CpuUtilizationStatus.ActiveCpuUtilization);
+                        var cpuData = Math.round(curStatus.CpuUtilizationStatus.TotalCpuUtilization);
                         cpuSeries.push([curStatus.Timestamp, cpuData]);
-                        coreSeries[0].push([curStatus.Timestamp, curStatus.CoreStatus.ActiveCores]);
-                        coreSeries[1].push([curStatus.Timestamp, curStatus.CoreStatus.CalculatingCores]);
-                        memorySeries[0].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.ActiveMemory / 1024)]);
+                        coreSeries[0].push([curStatus.Timestamp, curStatus.CoreStatus.TotalCores]);
+                        coreSeries[1].push([curStatus.Timestamp, curStatus.CoreStatus.UsedCores]);
+                        memorySeries[0].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.TotalMemory / 1024)]);
                         memorySeries[1].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.UsedMemory / 1024)]);
                     }
@@ -129,5 +144,4 @@
                         { data: memorySeries[1], label: "&nbsp;Used Memory", color: "LightPink" }
                     ];
-
                 });
             };
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/status.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/status.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/status.cshtml	(revision 12878)
@@ -88,5 +88,5 @@
                     <tr data-toggle="tooltip" data-placement="bottom" title="All calculating slaves that are allowed to calculate">
                         <td class="text-left">Calculating:</td>
-                        <td class="text-right">{{status.MemoryStatus.UsedMemory | kbToGB}} GB</td>
+                        <td class="text-right">{{status.MemoryStatus.CalculatingMemory | kbToGB}} GB</td>
                     </tr>
                 </table>
@@ -115,4 +115,54 @@
                     </tr>
                 </table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-lg-12">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <h3 class="panel-title">Status Overview</h3>
+            </div>
+            <div class="panel-body">
+                <div class="row">
+                    <div class="col-md-6">
+                        <table class="table table-no-border table-condensed table-auto-width table-content">
+                            <tr>
+                                <td class="text-left">Current Average Waiting Time:</td>
+                                <td class="text-right">{{status.TimeStatus.AvgWaitingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td class="text-left">Total CPU Time:</td>
+                                <td class="text-right">{{status.TimeStatus.TotalCpuTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td class="text-left">Since:</td>
+                                <td class="text-right">{{status.TimeStatus.BeginDate | toDate}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div class="col-md-6">
+                        <table class="table table-no-border table-condensed table-auto-width table-content">
+                            <tr>
+                                <td class="text-left">Minimum Calculating Time:</td>
+                                <td class="text-right">{{status.TimeStatus.MinCalculatingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td class="text-left">Maximum Calculating Time:</td>
+                                <td class="text-right">{{status.TimeStatus.MaxCalculatingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td class="text-left">Average Calculating Time:</td>
+                                <td class="text-right">{{status.TimeStatus.AvgCalculatingTime | toTimespan}}</td>
+                            </tr>
+                            <tr>
+                                <td class="text-left">Standard Deviation Calculating Time:</td>
+                                <td class="text-right">{{status.TimeStatus.StandardDeviationCalculatingTime | toTimespan}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
             </div>
         </div>
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/statusCtrl.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/statusCtrl.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/status/statusCtrl.js	(revision 12878)
@@ -103,5 +103,5 @@
                     $scope.cpu.knobData = Math.round(status.CpuUtilizationStatus.ActiveCpuUtilization);
                     $scope.core.knobData = Math.round(status.CoreStatus.CalculatingCores / status.CoreStatus.ActiveCores * 100);
-                    $scope.memory.knobData = Math.round(status.MemoryStatus.UsedMemory / status.MemoryStatus.ActiveMemory * 100);
+                    $scope.memory.knobData = Math.round(status.MemoryStatus.CalculatingMemory / status.MemoryStatus.ActiveMemory * 100);
                     // chart series
                     var cpuSeries = $scope.cpu.series[0].data.splice(0);
@@ -127,13 +127,8 @@
                     
                     cpuSeries.push([$scope.status.Timestamp, Math.round(status.CpuUtilizationStatus.TotalCpuUtilization)]);
-                    // charts are currently filled with old total/used data
-                    // start temporary
-                    var usedCores = status.CoreStatus.TotalCores - status.CoreStatus.FreeCores;
-                    var usedMemory = status.MemoryStatus.TotalMemory - status.MemoryStatus.FreeMemory;
-                    // end temporary
                     coreSeries[0].push([$scope.status.Timestamp, status.CoreStatus.TotalCores]);
-                    coreSeries[1].push([$scope.status.Timestamp, usedCores]);
+                    coreSeries[1].push([$scope.status.Timestamp, status.CoreStatus.UsedCores]);
                     memorySeries[0].push([$scope.status.Timestamp, Math.round(status.MemoryStatus.TotalMemory / 1024)]);
-                    memorySeries[1].push([$scope.status.Timestamp, Math.round(usedMemory / 1024)]);
+                    memorySeries[1].push([$scope.status.Timestamp, Math.round(status.MemoryStatus.UsedMemory / 1024)]);
                     $scope.cpu.series = [{ data: cpuSeries, label: "&nbsp;CPU Utilization", color: "#f7921d" }];
                     $scope.core.series = [
@@ -160,9 +155,9 @@
                     for (var i = 0; i < noOfStatus; ++i) {
                         var curStatus = status[i];
-                        var cpuData = Math.round(curStatus.CpuUtilizationStatus.ActiveCpuUtilization);
+                        var cpuData = Math.round(curStatus.CpuUtilizationStatus.TotalCpuUtilization);
                         cpuSeries.push([curStatus.Timestamp, cpuData]);
-                        coreSeries[0].push([curStatus.Timestamp, curStatus.CoreStatus.ActiveCores]);
-                        coreSeries[1].push([curStatus.Timestamp, curStatus.CoreStatus.CalculatingCores]);
-                        memorySeries[0].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.ActiveMemory / 1024)]);
+                        coreSeries[0].push([curStatus.Timestamp, curStatus.CoreStatus.TotalCores]);
+                        coreSeries[1].push([curStatus.Timestamp, curStatus.CoreStatus.UsedCores]);
+                        memorySeries[0].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.TotalMemory / 1024)]);
                         memorySeries[1].push([curStatus.Timestamp, Math.round(curStatus.MemoryStatus.UsedMemory / 1024)]);
                     }
Index: /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/status.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/status.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp.Status/3.3/status.js	(revision 12878)
@@ -23,5 +23,5 @@
             name: 'History',
             route: '#/status/history',
-            icon: 'glyphicon glyphicon-stats'
+            icon: 'glyphicon glyphicon-calendar'
         }]
     });
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/Configs/BundleConfig.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/Configs/BundleConfig.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/Configs/BundleConfig.cs	(revision 12878)
@@ -59,5 +59,5 @@
         // bootstrap
         "~/WebApp/libs/bootstrap/js/bootstrap.min.js",
-        // crypto js
+        // cryptojs
         "~/WebApp/libs/cryptojs/aes.js",
         // angular js
@@ -77,4 +77,5 @@
         "~/WebApp/libs/angularjs/loading-bar/loading-bar.js",
         "~/WebApp/libs/angularjs/ocLazyLoad/ocLazyLoad.min.js",
+        "~/WebApp/libs/angularjs/fittext/ng-FitText.js",
         // smoothScroll
         "~/WebApp/libs/smoothScroll/smoothScroll.js"
@@ -105,5 +106,6 @@
       PluginManager pluginManager = PluginManager.Instance;
       foreach (var plugin in pluginManager.Plugins) {
-        if (File.Exists(string.Format(@"{0}\{1}\{1}.js", PluginManager.PluginsDirectory, plugin.Name))) {
+        var path = Path.Combine(PluginManager.PluginsDirectory, plugin.Name, string.Concat(plugin.Name, ".js"));
+        if (File.Exists(path)) {
           jsFiles.Add(string.Format("WebApp/plugins/{0}/{0}.js", plugin.Name));
         }
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/Controllers/AppController.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/Controllers/AppController.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/Controllers/AppController.cs	(revision 12878)
@@ -20,4 +20,5 @@
 #endregion
 
+using System.Web;
 using System.Web.Mvc;
 
@@ -30,4 +31,5 @@
         return RedirectPermanent(Request.Url + "/");
       }
+      Response.Cache.SetCacheability(HttpCacheability.NoCache);
       return View("~/WebApp/shared/layout/layout.cshtml");
     }
@@ -38,10 +40,12 @@
 
     public ActionResult LoadSharedView(string directory, string view, string dateTime) {
-      // dateTime is optional to avoid browser caching
+      Response.Cache.SetCacheability(HttpCacheability.NoCache);
+      // dateTime is optional to definitly avoid browser caching
       return View(string.Format("~/WebApp/shared/{0}/{1}", directory, view));
     }
 
     public ActionResult LoadPluginView(string plugin, string view, string dateTime) {
-      // dateTime is optional to avoid browser caching
+      Response.Cache.SetCacheability(HttpCacheability.NoCache);
+      // dateTime is optional to definitly avoid browser caching
       return View(string.Format("~/WebApp/plugins/{0}/{1}", plugin, view));
     }
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/HeuristicLab.Services.WebApp-3.3.csproj
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/HeuristicLab.Services.WebApp-3.3.csproj	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/HeuristicLab.Services.WebApp-3.3.csproj	(revision 12878)
@@ -13,5 +13,5 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>HeuristicLab.Services.WebApp</RootNamespace>
-    <AssemblyName>HeuristicLab.Services.WebApp</AssemblyName>
+    <AssemblyName>HeuristicLab.Services.WebApp-3.3</AssemblyName>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <MvcBuildViews>false</MvcBuildViews>
@@ -118,6 +118,5 @@
     <Reference Include="System.Net.Http.WebRequest">
     </Reference>
-    <Reference Include="WebGrease, Version=1.6.5135.21930, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="WebGrease">
       <HintPath>..\..\packages\WebGrease.1.6.0\lib\WebGrease.dll</HintPath>
     </Reference>
@@ -146,4 +145,6 @@
     <Content Include="WebApp\libs\angularjs\angular-tablesort\angular-tablesort.js" />
     <Content Include="WebApp\libs\angularjs\angular-tablesort\tablesort.css" />
+    <Content Include="WebApp\libs\angularjs\fittext\ng-FitText.js" />
+    <Content Include="WebApp\libs\cryptojs\aes.js" />
     <Content Include="WebApp\main.js" />
     <Content Include="WebApp\helper.js" />
@@ -172,10 +173,22 @@
     <Content Include="WebApp\plugins\about\about.js" />
     <Content Include="WebApp\plugins\about\aboutCtrl.js" />
-    <Content Include="WebApp\plugins\login\login.css" />
-    <Content Include="WebApp\plugins\login\login.js" />
-    <Content Include="WebApp\plugins\login\loginCtrl.js" />
-    <Content Include="WebApp\plugins\plugins\pluginsExceptionCtrl.js" />
-    <Content Include="WebApp\plugins\plugins\plugins.js" />
-    <Content Include="WebApp\plugins\plugins\pluginsCtrl.js" />
+    <Content Include="WebApp\plugins\login\login.css">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugins\login\login.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugins\login\loginCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugins\plugins\pluginsExceptionCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugins\plugins\plugins.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\plugins\plugins\pluginsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="WebApp\plugins\plugins\pluginsService.js" />
     <Content Include="WebApp\shared\directives\flot.js" />
@@ -208,5 +221,7 @@
     <Content Include="HeuristicLab.snk" />
     <Content Include="Properties\AssemblyInfo.cs.frame" />
-    <Content Include="Web.config" />
+    <Content Include="Web.config">
+      <SubType>Designer</SubType>
+    </Content>
     <Content Include="Web.Debug.config">
       <DependentUpon>Web.config</DependentUpon>
@@ -219,4 +234,5 @@
     <Content Include="WebApp\libs\bootstrap\fonts\glyphicons-halflings-regular.woff" />
     <Content Include="WebApp\libs\bootstrap\fonts\glyphicons-halflings-regular.woff2" />
+    <Content Include="WebApp\shared\directives\scrollToTop.js" />
     <Content Include="WebApp\shared\filter\filters.js" />
     <Content Include="WebApp\shared\images\HeuristicLab-Logo.png" />
@@ -249,4 +265,5 @@
   </ItemGroup>
   <ItemGroup>
+    <None Include="Properties\PublishProfiles\WebApp.pubxml" />
     <Content Include="WebApp\plugins\about\about.cshtml" />
   </ItemGroup>
Index: unk/sources/HeuristicLab.Services.WebApp/3.3/HeuristicLab.Services.WebApp-3.3.csproj.user
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/HeuristicLab.Services.WebApp-3.3.csproj.user	(revision 12877)
+++ 	(revision )
@@ -1,28 +1,0 @@
-﻿<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ProjectExtensions>
-    <VisualStudio>
-      <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
-        <WebProjectProperties>
-          <StartPageUrl>
-          </StartPageUrl>
-          <StartAction>CurrentPage</StartAction>
-          <AspNetDebugging>True</AspNetDebugging>
-          <SilverlightDebugging>False</SilverlightDebugging>
-          <NativeDebugging>False</NativeDebugging>
-          <SQLDebugging>False</SQLDebugging>
-          <ExternalProgram>
-          </ExternalProgram>
-          <StartExternalURL>
-          </StartExternalURL>
-          <StartCmdLineArguments>
-          </StartCmdLineArguments>
-          <StartWorkingDirectory>
-          </StartWorkingDirectory>
-          <EnableENC>True</EnableENC>
-          <AlwaysStartWebServerOnDebug>True</AlwaysStartWebServerOnDebug>
-        </WebProjectProperties>
-      </FlavorProperties>
-    </VisualStudio>
-  </ProjectExtensions>
-</Project>
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/Plugin.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/Plugin.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/Plugin.cs	(revision 12878)
@@ -77,4 +77,5 @@
         if (!assemblies.Any())
           return;
+
         var assemblyPath = assemblies.First();
         AssemblyName = Path.GetFileName(assemblyPath);
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/PluginManager.cs
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/PluginManager.cs	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/PluginManager.cs	(revision 12878)
@@ -43,5 +43,7 @@
 
     public static string PluginsDirectory {
-      get { return string.Format(@"{0}WebApp\plugins", HttpRuntime.AppDomainAppPath); }
+      get {
+        return Path.Combine(HttpRuntime.AppDomainAppPath, "WebApp", "plugins");
+      }
     }
 
@@ -60,7 +62,7 @@
     private void OnFilesChanged(object sender, FileSystemEventArgs args) {
       string path = args.FullPath.Remove(0, PluginsDirectory.Length + 1);
-      var pathParts = path.Split(Path.PathSeparator);
+      var pathParts = path.Split(Path.DirectorySeparatorChar);
       string pluginName = pathParts[0];
-      if (pathParts.Length == 1) {
+      if (pathParts.Length <= 2) {
         switch (args.ChangeType) {
           case WatcherChangeTypes.Created:
@@ -75,5 +77,5 @@
             RenamedEventArgs renamedArgs = (RenamedEventArgs)args;
             string oldPath = renamedArgs.OldFullPath.Remove(0, PluginsDirectory.Length + 1);
-            var oldPathParts = oldPath.Split(Path.PathSeparator);
+            var oldPathParts = oldPath.Split(Path.DirectorySeparatorChar);
             string oldPluginName = oldPathParts[0];
             plugins.Remove(oldPluginName);
@@ -94,5 +96,5 @@
       Plugin plugin = LookupPlugin(name);
       if (plugin == null) {
-        string directory = string.Format(@"{0}\{1}", PluginsDirectory, name);
+        string directory = Path.Combine(PluginsDirectory, name);
         if (Directory.Exists(directory)) {
           plugin = new Plugin(name, directory, Configuration);
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.css
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.css	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.css	(revision 12878)
@@ -352,9 +352,4 @@
 }
 
-[ng-click],
-[data-ng-click],
-[x-ng-click] {
-    cursor: pointer;
-}
 
 .navbar-default .navbar-brand {
@@ -419,4 +414,11 @@
 .default-view-container {
   padding: 30px;
+  min-height: 300px;
+}
+
+.default-view-header {
+  padding: 30px;
+  background: #F5F5F5;
+  border-bottom: 1px solid #D3D3D3;
 }
 
@@ -434,2 +436,111 @@
   text-decoration: none;
 }
+
+/*remove button gradient*/
+.btn {
+  background-image: none;
+  filter: none;
+  -webkit-box-shadow: none !important;
+     -moz-box-shadow: none !important;
+       -o-box-shadow: none !important;
+          box-shadow: none !important;
+}
+
+.btn-default {
+  background-image: none;
+  filter: none;
+  -webkit-box-shadow: none !important;
+     -moz-box-shadow: none !important;
+       -o-box-shadow: none !important;
+          box-shadow: none !important;
+}
+
+.btn .btn-default {
+  background-image: none;
+  filter: none;
+  -webkit-box-shadow: none !important;
+     -moz-box-shadow: none !important;
+       -o-box-shadow: none !important;
+          box-shadow: none !important;
+}
+
+.btn-default.active, .btn-default:active{
+  border-color: #adadad;
+}
+
+
+
+.modal-dialog-center {
+    margin: 0;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+}
+
+.modal-header {
+  background: #fafafa;
+}
+
+.modal-body {
+    overflow-y: auto;
+}
+.modal-footer {
+    margin-top: 0;
+}
+
+@media (max-width: 767px) {
+  .modal-dialog-center {
+    width: 100%;
+  }
+}
+
+body.modal-open {
+    overflow: visible;
+}
+
+.table .progress {
+margin-bottom: 0px;
+}
+
+.default-filter-header {
+  padding: 30px;
+  background: #F5F5F5;
+  border-top: 1px solid #D3D3D3;
+  border-bottom: 1px solid #D3D3D3;
+}
+
+.default-tab-filter-header {
+  padding: 30px;
+  background: #F5F5F5;
+  border-bottom: 1px solid #D3D3D3;
+}
+
+.cursor-pointer:hover {
+  cursor: pointer
+}
+
+.app-modal-window .modal-dialog {
+  top: 15%;
+  z-index: 1050;
+}
+
+.pagination>li>a, 
+.pagination>li>span {
+  border-color: #ccc;
+}
+
+.pagination>.active>a, 
+.pagination>.active>a:focus, 
+.pagination>.active>span, 
+.pagination>.active>span:focus {
+  color: black;
+  background: #e0e0e0;
+  border-color: #adadad;
+}
+
+.pagination>.active>a:hover, 
+.pagination>.active>span:hover {
+  color: black;
+  border-color: #ccc;
+  background: #eeeeee;
+}
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/app.js	(revision 12878)
@@ -2,4 +2,5 @@
 var appName = 'app';
 var appPath = 'WebApp';
+var defaultPageUpdateInterval = 60000;
 
 // route, plugin, menu, section and main app
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/helper.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/helper.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/helper.js	(revision 12878)
@@ -7,4 +7,22 @@
 Number.prototype.zeropad = function (n) {
     return (new Array(n + 1).join("0") + this).slice(-n);
+};
+
+Number.prototype.toHHMMSS = function() {
+    d = this;
+    d = Number(d);
+    var years = Math.floor(d / 31536000);
+    var days = Math.floor(d / 86400 % 365);
+    var h = Math.floor(d / 3600 % 24);
+    var m = Math.floor(d % 3600 / 60);
+    var s = Math.floor(d % 3600 % 60);
+    var timeStr = "";
+    if (years > 0) {
+        timeStr = years + "y ";
+    }
+    if (days > 0) {
+        timeStr = timeStr + days + "d ";
+    }
+    return timeStr + h.zeropad(2) + ":" + m.zeropad(2) + ":" + s.zeropad(2);
 };
 
@@ -33,5 +51,4 @@
 };
 
-
 var CSharpDateToString = function (datetime) {
     var date = new Date(Date.parse(datetime));
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/libs/angularjs/fittext/ng-FitText.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/libs/angularjs/fittext/ng-FitText.js	(revision 12878)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/libs/angularjs/fittext/ng-FitText.js	(revision 12878)
@@ -0,0 +1,76 @@
+﻿/* ng-FitText.js v3.3.3
+ * https://github.com/patrickmarabeas/ng-FitText.js
+ *
+ * Original jQuery project: https://github.com/davatron5000/FitText.js
+ *
+ * Copyright 2015, Patrick Marabeas http://marabeas.io
+ * Released under the MIT license
+ * http://opensource.org/licenses/mit-license.php
+ *
+ * Date: 06/05/2015
+ */
+
+(function (window, document, angular, undefined) {
+
+    'use strict';
+
+    angular.module('ngFitText', [])
+      .value('config', {
+          'debounce': false,
+          'delay': 250,
+          'loadDelay': 10,
+          'min': undefined,
+          'max': undefined
+      })
+
+      .directive('fittext', ['$timeout', 'config', 'fitTextConfig', function ($timeout, config, fitTextConfig) {
+          return {
+              restrict: 'A',
+              scope: true,
+              link: function (scope, element, attrs) {
+                  angular.extend(config, fitTextConfig.config);
+
+                  element[0].style.display = 'inline-block';
+                  element[0].style.lineHeight = '1';
+
+                  var parent = element.parent();
+                  var compressor = attrs.fittext || 1;
+                  var loadDelay = attrs.fittextLoadDelay || config.loadDelay;
+                  var nl = element[0].querySelectorAll('[fittext-nl],[data-fittext-nl]').length || 1;
+                  var minFontSize = attrs.fittextMin || config.min || Number.NEGATIVE_INFINITY;
+                  var maxFontSize = attrs.fittextMax || config.max || Number.POSITIVE_INFINITY;
+
+                  var resizer = function () {
+                      element[0].style.fontSize = '10px';
+                      var ratio = element[0].offsetHeight / element[0].offsetWidth / nl;
+                      element[0].style.fontSize = Math.max(
+                        Math.min((parent[0].offsetWidth - 6) * ratio * compressor,
+                          parseFloat(maxFontSize)
+                        ),
+                        parseFloat(minFontSize)
+                      ) + 'px';
+                  };
+
+                  $timeout(function () { resizer() }, loadDelay);
+
+                  scope.$watch(attrs.ngModel, function () { resizer() });
+
+                  config.debounce
+                    ? angular.element(window).bind('resize', config.debounce(function () { scope.$apply(resizer) }, config.delay))
+                    : angular.element(window).bind('resize', function () { scope.$apply(resizer) });
+              }
+          }
+      }])
+
+      .provider('fitTextConfig', function () {
+          var self = this;
+          this.config = {};
+          this.$get = function () {
+              var extend = {};
+              extend.config = self.config;
+              return extend;
+          };
+          return this;
+      });
+
+})(window, document, angular);
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/about/about.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/about/about.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/about/about.cshtml	(revision 12878)
@@ -28,5 +28,5 @@
                 <div class="panel-body">
                     <p><strong>Support</strong>: <a href="App/RedirectUrl?url=http://groups.google.com/group/heuristiclab">HeuristicLab Google Group</a></p>
-                    <p><strong>Inquiries</strong>: ​<a ng-click="mailToSupport()">support(at)heuristiclab.com</a></p>
+                    <p><strong>Inquiries</strong>: ​<a class="cursor-pointer" ng-click="mailToSupport()">support(at)heuristiclab.com</a></p>
                     <p><strong>Heuristic and Evolutionary Algorithms Laboratory (HEAL)</strong>: <a href="App/RedirectUrl?url=http://heal.heuristiclab.com">​http://heal.heuristiclab.com</a></p>
                     <p><strong>HeuristicLab development site</strong>: <a href="App/RedirectUrl?url=http://dev.heuristiclab.com">http://dev.heuristiclab.com</a></p>
@@ -36,5 +36,5 @@
                         <span class="glyphicon glyphicon-phone-alt"></span> +43 50804 22030<br />
                         <span class="glyphicon glyphicon-print"></span> +43 50804 21599<br />
-                        <span class="glyphicon glyphicon-envelope"></span> <a ng-click="mailToStefanWagner()">stefan.wagner(at)heuristiclab.com</a><br />
+                        <span class="glyphicon glyphicon-envelope"></span> <a class="cursor-pointer" ng-click="mailToStefanWagner()">stefan.wagner(at)heuristiclab.com</a><br />
                     </p>
                 </div>
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/login/login.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/login/login.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/login/login.cshtml	(revision 12878)
@@ -14,5 +14,5 @@
                             <div class="input-group">
                                 <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
-                                <input type="text" class="form-control" name="Username" placeholder="User"
+                                <input type="text" class="form-control" name="Username" placeholder="Username"
                                        ng-model="user.Username">
                             </div>
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/plugins/plugins.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/plugins/plugins.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/plugins/plugins/plugins.cshtml	(revision 12878)
@@ -21,7 +21,7 @@
                                 <th>#</th>
                                 <th ts-criteria="Name">Name</th>
-                                <th ts-criteria ="AssemblyName">Assembly</th>
+                                <th ts-criteria="AssemblyName">Assembly</th>
                                 <th ts-criteria="LastReload">Last reload</th>
-                                <th>Status</th>
+                                <th class="text-center">Status</th>
                                 <th></th>
                             </tr>
@@ -33,5 +33,5 @@
                                 <td>{{plugin.AssemblyName}}</td>
                                 <td>{{plugin.LastReload}}</td>
-                                <td>
+                                <td class="text-center">
                                     <span ng-hide="plugin.Exception" class="glyphicon glyphicon glyphicon-ok" style="color: green"></span>
                                     <span ng-show="plugin.Exception" class="glyphicon glyphicon glyphicon-remove" style="color: darkred"
@@ -39,5 +39,5 @@
                                 </td>
                                 <td>
-                                    <a ng-href="" data-ng-click="reloadPlugin(plugin.Name)">Reload</a>
+                                    <a ng-href="" class="cursor-pointer" data-ng-click="reloadPlugin(plugin.Name)">Reload</a>
                                 </td>
                             </tr>
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/filter/filters.js
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/filter/filters.js	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/filter/filters.js	(revision 12878)
@@ -1,3 +1,3 @@
-﻿(function () {
+﻿(function() {
     var module = appMainPlugin.getAngularModule();
     module.filter('kbToGB', function () {
Index: /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/layout/layout.cshtml
===================================================================
--- /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/layout/layout.cshtml	(revision 12877)
+++ /trunk/sources/HeuristicLab.Services.WebApp/3.3/WebApp/shared/layout/layout.cshtml	(revision 12878)
@@ -24,9 +24,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta http-equiv="pragma" content="no-cache" />
     <title>HeuristicLab Hive</title>
     <link href="~/WebApp/HeuristicLab.ico" rel="shortcut icon" type="image/x-icon" />
     @Styles.Render("~/Bundles/Vendors/css")
     @Styles.Render("~/Bundles/WebApp/css")
-    <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700">
+    <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700">
     <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
     <!--[if lt IE 9]>
@@ -63,15 +64,12 @@
                 <div class="navbar-content">
                     <ul class="nav navbar-nav navbar-right">
-                        @if (Request.IsAuthenticated)
-                        {
+                        @if (Request.IsAuthenticated) {
                             <li>
                                 <span class="navbar-text">Hello @User.Identity.Name!</span>
                             </li>
                             <li>
-                                <a ng-href="" data-ng-click="logout()">Logout</a>
+                                <a ng-href="#" data-ng-click="logout()">Logout</a>
                             </li>
-                        }
-                        else
-                        {
+                        } else {
                             <li>
                                 <a ng-href="#/login" data-ng-click="hideMenu()">Login</a>
@@ -97,7 +95,7 @@
 </div>
 <script type="text/javascript">
-$('.navbar-collapse').on('click', 'li a', function () {
-    $('.navbar-collapse').collapse('hide');
-});
+    $('.navbar-collapse').on('click', 'li a', function () {
+        $('.navbar-collapse').collapse('hide');
+    });
 </script>
 </body>
Index: /trunk/sources/Installers/HiveJanitorServiceInstaller.nsi
===================================================================
--- /trunk/sources/Installers/HiveJanitorServiceInstaller.nsi	(revision 12877)
+++ /trunk/sources/Installers/HiveJanitorServiceInstaller.nsi	(revision 12878)
@@ -51,4 +51,5 @@
 	File "${JANITORBUILDPATH}\HeuristicLab.PluginInfrastructure-3.3.dll"
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Access.dll"
+	File "${JANITORBUILDPATH}\HeuristicLab.Services.Access.DataAccess.dll"
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Hive.DataAccess-3.3.dll"
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Hive.JanitorService-3.3.exe"
