diff --git a/src/main/java/de/bps/onyx/util/ExamPoolManagerProvider.java b/src/main/java/de/bps/onyx/util/ExamPoolManagerProvider.java
index 8812e7ceedffa35f0b4e85db46970815acec2b45..c2d1bff378a21ee5f8af48a7f557fba86bf7632e 100644
--- a/src/main/java/de/bps/onyx/util/ExamPoolManagerProvider.java
+++ b/src/main/java/de/bps/onyx/util/ExamPoolManagerProvider.java
@@ -56,6 +56,7 @@ public class ExamPoolManagerProvider implements MessageListener {
 
 	private final OLog log = Tracing.createLoggerFor(ExamPoolManagerProvider.class);
 	private final ExamPoolManager examPoolManager;
+	private TaskExecutorManager taskExecutorManager;
 	private ConnectionFactory connectionFactory;
 	private Connection connection;
 	private Queue examControlQueue;
@@ -78,7 +79,7 @@ public class ExamPoolManagerProvider implements MessageListener {
 			if (message instanceof ObjectMessage) {
 				ObjectMessage objectMessage = (ObjectMessage) message;
 				final JMSExamMessage examMessage = (JMSExamMessage) objectMessage.getObject();
-				TaskExecutorManager.getInstance().runTask(new Runnable() {
+				taskExecutorManager.execute(new Runnable() {
 					@Override
 					public void run() {
 						handleRemoteMessage(examMessage, correlationID, replyTo);
@@ -188,6 +189,12 @@ public class ExamPoolManagerProvider implements MessageListener {
 		this.examControlQueue = examControlQueue;
 	}
 
+	/**
+	 * [used by Spring]
+	 */
+	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
+		this.taskExecutorManager = taskExecutorManager;
+	}
 
 	public void springInit() throws JMSException {
 		connection = connectionFactory.createConnection();
diff --git a/src/main/java/de/bps/onyx/util/_spring/examControlContext.xml b/src/main/java/de/bps/onyx/util/_spring/examControlContext.xml
index 0d4a688f4e598dd4ba24de4232e73306de5501cc..43c37ccfaf31f15f495e9a5aa150a9ab6fdfa8db 100644
--- a/src/main/java/de/bps/onyx/util/_spring/examControlContext.xml
+++ b/src/main/java/de/bps/onyx/util/_spring/examControlContext.xml
@@ -17,6 +17,7 @@
 		<constructor-arg name="examPoolManager" ref="examPoolManagerServer"/>
 		<property name="connectionFactory" ref="examControlConnectionFactory"/>
 		<property name="searchQueue" ref="examControlQueue"/>
+		<property name="taskExecutorManager" ref="taskExecutorManager"/>
 	</bean>
 		
 	<bean id="examPoolManagerServer" class="de.bps.onyx.util.ExamPoolManagerServer" lazy-init="true" />
diff --git a/src/main/java/org/olat/admin/sysinfo/FileSystemTestController.java b/src/main/java/org/olat/admin/sysinfo/FileSystemTestController.java
index c6aaa94220108038541ce5cb206a70fcec6e4b97..1b1de07c83f87ef1082968d9f4a705ef4ccda99b 100644
--- a/src/main/java/org/olat/admin/sysinfo/FileSystemTestController.java
+++ b/src/main/java/org/olat/admin/sysinfo/FileSystemTestController.java
@@ -29,6 +29,7 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.taskExecutor.TaskExecutorManager;
 import org.olat.core.gui.UserRequest;
@@ -85,6 +86,8 @@ public class FileSystemTestController extends BasicController implements Generic
 	
 	public static final OLATResourceable ORES_FILESYSTEMTEST = OresHelper.createOLATResourceableType(FileSystemTestController.class);
 
+	private final TaskExecutorManager taskExecutorManager;
+	
 	/**
 	 * Controlls user session in admin view.
 	 * 
@@ -95,7 +98,7 @@ public class FileSystemTestController extends BasicController implements Generic
 		super(ureq, wControl);
 		
 		testBaseDir = FolderConfig.getCanonicalTmpDir() + File.separator + TEST_BASEDIR_NAME;
-		
+		taskExecutorManager = CoreSpringFactory.getImpl(TaskExecutorManager.class);
 		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, null, ORES_FILESYSTEMTEST);
 		
 		myContent = createVelocityContainer("filesystemtest");
@@ -205,7 +208,7 @@ public class FileSystemTestController extends BasicController implements Generic
 				}
 			}
 		};
-		TaskExecutorManager.getInstance().runTask(fileWritterThread);
+		taskExecutorManager.execute(fileWritterThread);
 	}
 
 
@@ -297,7 +300,7 @@ public class FileSystemTestController extends BasicController implements Generic
 				}
 			}
 		};
-		TaskExecutorManager.getInstance().runTask(fileWritterThread);
+		taskExecutorManager.execute(fileWritterThread);
 	}
 
 
diff --git a/src/main/java/org/olat/admin/user/delete/DirectDeleteController.java b/src/main/java/org/olat/admin/user/delete/DirectDeleteController.java
index 12f116cba1310d849ceb52c2a1d3906befb03d83..22446db130f611a18c50f1c9396f5803cd1f0968 100644
--- a/src/main/java/org/olat/admin/user/delete/DirectDeleteController.java
+++ b/src/main/java/org/olat/admin/user/delete/DirectDeleteController.java
@@ -104,19 +104,11 @@ public class DirectDeleteController extends BasicController {
 					showError("msg.selectionempty");
 					return;
 				}
-				if (!UserDeletionManager.getInstance().isReadyToDelete()) {
-					showInfo("info.is.not.ready.to.delete");
-					return;
-				}
 				String names = buildUserNameList(toDelete);
 				deleteConfirmController = activateOkCancelDialog(ureq, null, translate("readyToDelete.delete.confirm", names), deleteConfirmController);
 				return;
 			} else if (event instanceof SingleIdentityChosenEvent) {
 				// single choose event may come from autocompleter user search
-				if (!UserDeletionManager.getInstance().isReadyToDelete()) {
-					showInfo("info.is.not.ready.to.delete");
-					return;
-				}
 				SingleIdentityChosenEvent uce = (SingleIdentityChosenEvent) event;
 				toDelete = new ArrayList<Identity>();
 				toDelete.add(uce.getChosenIdentity());
diff --git a/src/main/java/org/olat/admin/user/delete/ReadyToDeleteController.java b/src/main/java/org/olat/admin/user/delete/ReadyToDeleteController.java
index 4a8d08f8d10cfb866163d347bc42d2815343e918..fa68507510c82e106770f98b04db90d084789db6 100644
--- a/src/main/java/org/olat/admin/user/delete/ReadyToDeleteController.java
+++ b/src/main/java/org/olat/admin/user/delete/ReadyToDeleteController.java
@@ -146,10 +146,6 @@ public class ReadyToDeleteController extends BasicController {
 
 	private void handleDeleteButtonEvent(UserRequest ureq, TableMultiSelectEvent tmse) {
 		if (tdm.getObjects(tmse.getSelection()).size() != 0) {
-			if (!UserDeletionManager.getInstance().isReadyToDelete()) {
-				showInfo("info.is.not.ready.to.delete");
-				return;
-			}
 			readyToDeleteIdentities = tdm.getObjects(tmse.getSelection());
 			deleteConfirmController = activateOkCancelDialog(ureq, null, translate("readyToDelete.delete.confirm", getUserlistAsString(readyToDeleteIdentities)), deleteConfirmController);
 			return;
diff --git a/src/main/java/org/olat/admin/user/delete/service/DeleteUserDataTask.java b/src/main/java/org/olat/admin/user/delete/service/DeleteUserDataTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..206a583c7ef1e5527514c823c7e68680dcd235c5
--- /dev/null
+++ b/src/main/java/org/olat/admin/user/delete/service/DeleteUserDataTask.java
@@ -0,0 +1,210 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <p>
+* Licensed under the Apache License, Version 2.0 (the "License"); <br>
+* you may not use this file except in compliance with the License.<br>
+* You may obtain a copy of the License at
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <p>
+* Unless required by applicable law or agreed to in writing,<br>
+* software distributed under the License is distributed on an "AS IS" BASIS, <br>
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+* See the License for the specific language governing permissions and <br>
+* limitations under the License.
+* <p>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+package org.olat.admin.user.delete.service;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
+import org.olat.core.commons.taskExecutor.LongRunnable;
+import org.olat.core.id.Identity;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.FileUtils;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.PersistingCourseImpl;
+import org.olat.course.nodes.ProjectBrokerCourseNode;
+import org.olat.course.nodes.TACourseNode;
+import org.olat.course.nodes.ta.DropboxController;
+import org.olat.course.nodes.ta.ReturnboxController;
+import org.olat.ims.qti.editor.QTIEditorPackageImpl;
+import org.olat.resource.OLATResource;
+import org.olat.resource.OLATResourceManager;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class DeleteUserDataTask implements LongRunnable {
+	private static final long serialVersionUID = 4278304131373256050L;
+
+	private static final OLog log = Tracing.createLoggerFor(DeleteUserDataTask.class);
+
+	private Long identityKey;
+	private String newDeletedUserName;
+	
+	public DeleteUserDataTask(Long identityKey, String newDeletedUserName) {
+		this.identityKey = identityKey;
+		this.newDeletedUserName = newDeletedUserName;
+	}
+	
+	@Override
+	public void run() {
+		long startTime = System.currentTimeMillis();
+		Identity identity = CoreSpringFactory.getImpl(BaseSecurity.class).loadIdentityByKey(identityKey);
+		deleteAllDropboxReturnboxFilesOf(identity);
+		deleteHomesMetaDataOf(identity, newDeletedUserName);
+		deleteAllTempQtiEditorFilesOf(identity);
+		log.info("Finished UserFileDeletionManager thread for identity=" + identity + " in " + (System.currentTimeMillis() - startTime) + " (ms)");
+	}
+	
+	private void deleteAllTempQtiEditorFilesOf(Identity identity) {
+		// Temp QTI-editor File path e.g. /usr/local/olatfs/olat/olatdata/tmp/qtieditor/schuessler
+		File userTempQtiEditorDir = new File(QTIEditorPackageImpl.getQTIEditorBaseDir(),identity.getName());
+		if (userTempQtiEditorDir.exists()) {
+			FileUtils.deleteDirsAndFiles(userTempQtiEditorDir, true, true); 
+			log.audit("User-Deletion: identity=" + identity.getName() +" : QTI editor temp files deleted under dir=" + userTempQtiEditorDir.getAbsolutePath());
+		}
+	}
+
+	private void deleteHomesMetaDataOf(Identity identity, String newDeletedUserName) {
+		final boolean debug = log.isDebug();
+		String metaRootDirPath = FolderConfig.getCanonicalMetaRoot();
+		File metaRootDir = new File(metaRootDirPath);
+		File[] homesDirs = metaRootDir.listFiles( new UserFileFilter(FolderConfig.getUserHomes().substring(1)) );
+		if ( (homesDirs != null) && (homesDirs.length == 1) ) {
+			File[] userDirs = homesDirs[0].listFiles( new UserFileFilter(identity.getName()) );
+			// userDirs can contain only home-dir of deleted user 
+			if (userDirs.length > 0) {
+				if (debug) log.debug("deleteHomesMetaDataOf: Delete meta-data homes/" + identity.getName() + " dir, process file=" + userDirs[0].getAbsolutePath());
+				// the meta-data under home/<USER> can be deleted and must not be renamed
+				FileUtils.deleteDirsAndFiles(userDirs[0], true, true); 			
+				log.audit("User-Deletion: Delete meta-data homes directory for identity=" + identity.getName()+ " directory=" + userDirs[0].getAbsolutePath());
+			} else {
+				log.debug("deleteHomesMetaDataOf: Found no '" + identity.getName() + "' directory at directory=" + homesDirs[0].getAbsolutePath());
+			}
+		}
+	}
+
+	/**
+	 * Delete all 'dropboxes' or 'returnboxes' directories for certain user in the course-file structure.
+	 * 
+	 * @param identity
+	 */
+	private void deleteAllDropboxReturnboxFilesOf(Identity identity) {
+		final boolean debug = log.isDebug();
+		
+		File courseBaseDir = getCourseBaseContainer();
+		// loop over all courses path e.g. olatdata\bcroot\course\78931391428316\dropboxes\78933379704296\deltest 
+		//                                                                       ^^^^^^^^^ dirTypeName
+		File[] courseDirs = courseBaseDir.listFiles();
+		// 1. loop over all course-id e.g. 78931391428316
+		for (int courseIndex = 0; courseIndex < courseDirs.length; courseIndex++) {
+			if (debug) log.debug("process dir=" + courseDirs[courseIndex].getAbsolutePath());
+			String currentCourseId = courseDirs[courseIndex].getName();
+			if (debug) log.debug("currentCourseId=" + currentCourseId);
+			if (courseDirs[courseIndex].isDirectory()) {
+				File[] dropboxReturnboxDirs = courseDirs[courseIndex].listFiles(new DropboxReturnboxFilter());
+				// 2. loop over all dropbox and returnbox  in course-folder
+				for (int dropboxIndex = 0; dropboxIndex < dropboxReturnboxDirs.length; dropboxIndex++) {
+					File[] nodeDirs = dropboxReturnboxDirs[dropboxIndex].listFiles();
+					// 3. loop over all node-id e.g. 78933379704296
+					for (int nodeIndex = 0; nodeIndex < nodeDirs.length; nodeIndex++) {
+						if (debug)  log.debug("process dir=" + nodeDirs[nodeIndex].getAbsolutePath());
+						String currentNodeId =  nodeDirs[nodeIndex].getName();
+						if (debug) log.debug("currentNodeId=" + currentNodeId);
+						ICourse currentCourse = null;
+						try {
+							Long resId = Long.parseLong(currentCourseId);
+							//check if the course exists
+							OLATResource resource = OLATResourceManager.getInstance().findResourceable(resId, "CourseModule");
+							if(resource != null) {
+								currentCourse = CourseFactory.loadCourse(resId);
+							} else {
+								log.warn("course with resid=" + currentCourseId + " has a folder but no resource/repository entry", null);
+							}
+						} catch (Exception e) {
+							log.error("could not load course with resid="+currentCourseId,e);
+						}
+						if (currentCourse != null) {
+							if (isTaskNode(currentCourse, currentNodeId)) {
+								if (debug) log.debug("found TACourseNode path=" + nodeDirs[nodeIndex].getAbsolutePath());
+								deleteUserDirectory(identity, nodeDirs[nodeIndex]);
+							} else if (isProjectBrokerNode(currentCourse, currentNodeId)) {
+								if (debug) log.debug("found ProjectBrokerCourseNode path=" + nodeDirs[nodeIndex].getAbsolutePath());
+								// addional loop over project-id
+								File[] projectDirs = nodeDirs[nodeIndex].listFiles();
+								for (int projectIndex = 0; projectIndex < projectDirs.length; projectIndex++) {
+									deleteUserDirectory(identity, projectDirs[projectIndex]);
+								}
+							} else {
+								log.warn("found dropbox or returnbox and node-type is NO Task- or ProjectBroker-Type courseId=" + currentCourseId + " nodeId=" + currentNodeId, null);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	private boolean isProjectBrokerNode(ICourse currentCourse, String currentNodeId) {
+		return currentCourse.getRunStructure().getNode(currentNodeId) instanceof ProjectBrokerCourseNode;
+	}
+
+	private boolean isTaskNode(ICourse currentCourse, String currentNodeId) {
+		return currentCourse.getRunStructure().getNode(currentNodeId) instanceof TACourseNode;
+	}
+
+	private void deleteUserDirectory(Identity identity, File directory) {
+		File[] userDirs = directory.listFiles( new UserFileFilter(identity.getName()) );
+		// 4. loop over all user-dir e.g. deltest (only once)
+		if (userDirs.length > 0) {
+			if (log.isDebug()) log.debug("process dir=" + userDirs[0].getAbsolutePath());
+			// ok found a directory of a user => delete it
+			FileUtils.deleteDirsAndFiles(userDirs[0], true, true); 
+			log.audit("User-Deletion: identity=" + identity.getName() +" : User file data deleted under dir=" + userDirs[0].getAbsolutePath());
+			if (userDirs.length > 1) log.error("Found more than one sub-dir for user=" + identity.getName() + " path=" + userDirs[0].getAbsolutePath(), null);
+		}
+	}
+	
+	/**
+	 * 
+	 * @return e.g. olatdata\bcroot\course\
+	 */
+	private File getCourseBaseContainer() {
+		OlatRootFolderImpl courseRootContainer = new OlatRootFolderImpl(File.separator + PersistingCourseImpl.COURSE_ROOT_DIR_NAME + File.separator, null);
+		return courseRootContainer.getBasefile(); 
+	}
+	
+	private static class DropboxReturnboxFilter implements FilenameFilter {
+		@Override
+		public boolean accept(File dir, String name) {
+			// don't add overlayLocales as selectable availableLanguages
+			// (LocaleStrings_de__VENDOR.properties)
+			if (   name.equals(ReturnboxController.RETURNBOX_DIR_NAME) 
+					|| name.equals(DropboxController.DROPBOX_DIR_NAME)) { 
+				return true; 
+			} else {
+				return false;
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
index c785f614e444f7e20976b64c350a05f54ea6a976..f11bed838ac73980197db7ccd10667cd12b97473 100644
--- a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
+++ b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
@@ -70,7 +70,6 @@ import org.olat.repository.delete.service.DeletionModule;
 import org.olat.user.UserDataDeletable;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
-import org.springframework.beans.factory.annotation.Autowired;
 
 
 /**
@@ -250,13 +249,6 @@ public class UserDeletionManager extends BasicManager {
 		return dbq.list();
 	}
 	
-	/**
-	 * 
-	 * @return true when user can be deleted (non deletion-process is still running)
-	 */
-	public boolean isReadyToDelete() {
-		return UserFileDeletionManager.isReadyToDelete();
-	}
 	/**
 	 * Delete all user-data in registered deleteable resources.
 	 * @param identity
@@ -274,21 +266,7 @@ public class UserDeletionManager extends BasicManager {
 		
 		// FIXME: it would be better to call the mangers over a common interface which would not need to have references to all mangers here
 		if (!managersInitialized) {
-			//HomePageConfigManagerImpl.getInstance();
-			//DisplayPortraitManager.getInstance();
-			//NoteManager.getInstance();
-			//PropertyManager.getInstance();
-			//BookmarkManager.getInstance();
-			//NotificationsManager.getInstance();
-			//PersonalFolderManager.getInstance();
-			//IQManager.getInstance();
-			//QTIResultManager.getInstance();
-			//BusinessGroupManagerImpl.getInstance();
-			//RepositoryDeletionManager.getInstance();
-			//CatalogManager.getInstance();
 			CalendarManagerFactory.getInstance(); //the only one that left for refactoring
-			//EfficiencyStatementManager.getInstance();
-			//UserFileDeletionManager.getInstance();
 			managersInitialized = true;
 		}
 		
@@ -469,7 +447,7 @@ public class UserDeletionManager extends BasicManager {
 	 * @param keepUserLoginAfterDeletion The keepUserLoginAfterDeletion to set.
 	 */
 	public void setKeepUserLoginAfterDeletion(boolean keepUserLoginAfterDeletion) {
-		this.keepUserLoginAfterDeletion = keepUserLoginAfterDeletion;
+		UserDeletionManager.keepUserLoginAfterDeletion = keepUserLoginAfterDeletion;
 	}
 
 	/**
@@ -477,7 +455,7 @@ public class UserDeletionManager extends BasicManager {
 	 * @param keepUserEmailAfterDeletion The keepUserEmailAfterDeletion to set.
 	 */
 	public void setKeepUserEmailAfterDeletion(boolean keepUserEmailAfterDeletion) {
-		this.keepUserEmailAfterDeletion = keepUserEmailAfterDeletion;
+		UserDeletionManager.keepUserEmailAfterDeletion = keepUserEmailAfterDeletion;
 	}
 
 	public static boolean isKeepUserLoginAfterDeletion() {
diff --git a/src/main/java/org/olat/admin/user/delete/service/UserFileDeletionManager.java b/src/main/java/org/olat/admin/user/delete/service/UserFileDeletionManager.java
index 7ac22e8cf466c3a19578ccb0b9fa64f943397bae..2b8bfd3999f3ffafba58dc3b469af994fba4b203 100644
--- a/src/main/java/org/olat/admin/user/delete/service/UserFileDeletionManager.java
+++ b/src/main/java/org/olat/admin/user/delete/service/UserFileDeletionManager.java
@@ -27,24 +27,10 @@ package org.olat.admin.user.delete.service;
 
 import java.io.File;
 import java.io.FilenameFilter;
-import java.util.concurrent.atomic.AtomicInteger;
 
-import org.olat.core.commons.modules.bc.FolderConfig;
-import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
 import org.olat.core.commons.taskExecutor.TaskExecutorManager;
 import org.olat.core.id.Identity;
 import org.olat.core.manager.BasicManager;
-import org.olat.core.util.FileUtils;
-import org.olat.course.CourseFactory;
-import org.olat.course.ICourse;
-import org.olat.course.PersistingCourseImpl;
-import org.olat.course.nodes.ProjectBrokerCourseNode;
-import org.olat.course.nodes.TACourseNode;
-import org.olat.course.nodes.ta.DropboxController;
-import org.olat.course.nodes.ta.ReturnboxController;
-import org.olat.ims.qti.editor.QTIEditorPackageImpl;
-import org.olat.resource.OLATResource;
-import org.olat.resource.OLATResourceManager;
 import org.olat.user.UserDataDeletable;
 
 
@@ -73,176 +59,10 @@ public class UserFileDeletionManager extends BasicManager implements UserDataDel
 	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
 		this.taskExecutorManager = taskExecutorManager;
 	}
-	
-	public static final AtomicInteger numberOfRunningFileDeletionThreads = new AtomicInteger(); 
 
   public void deleteUserData(final Identity identity, final String newDeletedUserName) {
-  	numberOfRunningFileDeletionThreads.incrementAndGet();
-  	logDebug("deleteUserData numberOfRunningFileDeletionThreads.get()=" + numberOfRunningFileDeletionThreads.get());
-  	taskExecutorManager.runTask(new Runnable() {
-
-    	@Override
-    	public void run() {
-      	logDebug("run start numberOfRunningFileDeletionThreads.get()=" + numberOfRunningFileDeletionThreads.get());
-    		long startTime = 0 ;
-    		logInfo("Start UserFileDeletionManager thread for identity=" + identity);
-    		if (isLogDebugEnabled()) startTime = System.currentTimeMillis();
-    		deleteAllDropboxReturnboxFilesOf(identity);
-    		if (isLogDebugEnabled()) {
-    			logDebug("User-Deletion: deleteAllDropboxFiles takes " + (System.currentTimeMillis() - startTime) + "ms");
-    			startTime = System.currentTimeMillis();
-    		}
-    		deleteHomesMetaDataOf(identity, newDeletedUserName);
-    		if (isLogDebugEnabled()) {
-    			logDebug("User-Deletion: renameAllFileMetadata takes " + (System.currentTimeMillis() - startTime) + "ms");
-    			startTime = System.currentTimeMillis();
-    		}
-    		deleteAllTempQtiEditorFilesOf(identity);
-    		if (isLogDebugEnabled()) logDebug("User-Deletion: deleteAllTempQtiEditorFiles takes " + (System.currentTimeMillis() - startTime) + "ms");
-    		logInfo("Finished UserFileDeletionManager thread for identity=" + identity);
-    		UserFileDeletionManager.numberOfRunningFileDeletionThreads.decrementAndGet();
-    		logDebug("run end numberOfRunningFileDeletionThreads.get()=" + numberOfRunningFileDeletionThreads.get());
-   	}
-    });
+  	taskExecutorManager.execute(new DeleteUserDataTask(identity.getKey(), newDeletedUserName));
   }
-  
-  /**
-   * 
-   * @return true if no deletion-thread is running
-   */
-  public static boolean isReadyToDelete() {
-  	return numberOfRunningFileDeletionThreads.get() == 0;
-  }
-
-	private void deleteAllTempQtiEditorFilesOf(Identity identity) {
-		// Temp QTI-editor File path e.g. /usr/local/olatfs/olat/olatdata/tmp/qtieditor/schuessler
-		File userTempQtiEditorDir = new File(QTIEditorPackageImpl.getQTIEditorBaseDir(),identity.getName());
-		if (userTempQtiEditorDir.exists()) {
-			FileUtils.deleteDirsAndFiles(userTempQtiEditorDir, true, true); 
-			logAudit("User-Deletion: identity=" + identity.getName() +" : QTI editor temp files deleted under dir=" + userTempQtiEditorDir.getAbsolutePath());
-		}
-	}
-
-	private void deleteHomesMetaDataOf(Identity identity, String newDeletedUserName) {
-		String metaRootDirPath = FolderConfig.getCanonicalMetaRoot();
-		File metaRootDir = new File(metaRootDirPath);
-		File[] homesDirs = metaRootDir.listFiles( new UserFileFilter(FolderConfig.getUserHomes().substring(1)) );
-		if ( (homesDirs != null) && (homesDirs.length == 1) ) {
-			File[] userDirs = homesDirs[0].listFiles( new UserFileFilter(identity.getName()) );
-			// userDirs can contain only home-dir of deleted user 
-			if (userDirs.length > 0) {
-				if (isLogDebugEnabled()) logDebug("deleteHomesMetaDataOf: Delete meta-data homes/" + identity.getName() + " dir, process file=" + userDirs[0].getAbsolutePath());
-				// the meta-data under home/<USER> can be deleted and must not be renamed
-				FileUtils.deleteDirsAndFiles(userDirs[0], true, true); 			
-				logAudit("User-Deletion: Delete meta-data homes directory for identity=" + identity.getName()+ " directory=" + userDirs[0].getAbsolutePath());
-			} else {
-				logDebug("deleteHomesMetaDataOf: Found no '" + identity.getName() + "' directory at directory=" + homesDirs[0].getAbsolutePath());
-			}
-		}
-	}
-
-	/**
-	 * Delete all 'dropboxes' or 'returnboxes' directories for certain user in the course-file structure.
-	 * 
-	 * @param identity
-	 */
-	private void deleteAllDropboxReturnboxFilesOf(Identity identity) {
-		File courseBaseDir = getCourseBaseContainer();
-		// loop over all courses path e.g. olatdata\bcroot\course\78931391428316\dropboxes\78933379704296\deltest 
-		//                                                                       ^^^^^^^^^ dirTypeName
-		File[] courseDirs = courseBaseDir.listFiles();
-		// 1. loop over all course-id e.g. 78931391428316
-		for (int courseIndex = 0; courseIndex < courseDirs.length; courseIndex++) {
-			if (isLogDebugEnabled()) logDebug("process dir=" + courseDirs[courseIndex].getAbsolutePath());
-			String currentCourseId = courseDirs[courseIndex].getName();
-			if (isLogDebugEnabled()) logDebug("currentCourseId=" + currentCourseId);
-			if (courseDirs[courseIndex].isDirectory()) {
-				File[] dropboxReturnboxDirs = courseDirs[courseIndex].listFiles(dropboxReturnboxFilter);
-				// 2. loop over all dropbox and returnbox  in course-folder
-				for (int dropboxIndex = 0; dropboxIndex < dropboxReturnboxDirs.length; dropboxIndex++) {
-					File[] nodeDirs = dropboxReturnboxDirs[dropboxIndex].listFiles();
-					// 3. loop over all node-id e.g. 78933379704296
-					for (int nodeIndex = 0; nodeIndex < nodeDirs.length; nodeIndex++) {
-						if (isLogDebugEnabled())  logDebug("process dir=" + nodeDirs[nodeIndex].getAbsolutePath());
-						String currentNodeId =  nodeDirs[nodeIndex].getName();
-						if (isLogDebugEnabled()) logDebug("currentNodeId=" + currentNodeId);
-						ICourse currentCourse = null;
-						try {
-							Long resId = Long.parseLong(currentCourseId);
-							//check if the course exists
-							OLATResource resource = OLATResourceManager.getInstance().findResourceable(resId, "CourseModule");
-							if(resource != null) {
-								currentCourse = CourseFactory.loadCourse(resId);
-							} else {
-								logWarn("course with resid=" + currentCourseId + " has a folder but no resource/repository entry", null);
-							}
-						} catch (Exception e) {
-							logError("could not load course with resid="+currentCourseId,e);
-						}
-						if (currentCourse != null) {
-							if (isTaskNode(currentCourse, currentNodeId)) {
-								if (isLogDebugEnabled()) logDebug("found TACourseNode path=" + nodeDirs[nodeIndex].getAbsolutePath());
-								deleteUserDirectory(identity, nodeDirs[nodeIndex]);
-							} else if (isProjectBrokerNode(currentCourse, currentNodeId)) {
-								if (isLogDebugEnabled()) logDebug("found ProjectBrokerCourseNode path=" + nodeDirs[nodeIndex].getAbsolutePath());
-								// addional loop over project-id
-								File[] projectDirs = nodeDirs[nodeIndex].listFiles();
-								for (int projectIndex = 0; projectIndex < projectDirs.length; projectIndex++) {
-									deleteUserDirectory(identity, projectDirs[projectIndex]);
-								}
-							} else {
-								logWarn("found dropbox or returnbox and node-type is NO Task- or ProjectBroker-Type courseId=" + currentCourseId + " nodeId=" + currentNodeId, null);
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	private boolean isProjectBrokerNode(ICourse currentCourse, String currentNodeId) {
-		return currentCourse.getRunStructure().getNode(currentNodeId) instanceof ProjectBrokerCourseNode;
-	}
-
-	private boolean isTaskNode(ICourse currentCourse, String currentNodeId) {
-		return currentCourse.getRunStructure().getNode(currentNodeId) instanceof TACourseNode;
-	}
-
-	private void deleteUserDirectory(Identity identity, File directory) {
-		File[] userDirs = directory.listFiles( new UserFileFilter(identity.getName()) );
-		// 4. loop over all user-dir e.g. deltest (only once)
-		if (userDirs.length > 0) {
-			if (isLogDebugEnabled()) logDebug("process dir=" + userDirs[0].getAbsolutePath());
-			// ok found a directory of a user => delete it
-			FileUtils.deleteDirsAndFiles(userDirs[0], true, true); 
-			logAudit("User-Deletion: identity=" + identity.getName() +" : User file data deleted under dir=" + userDirs[0].getAbsolutePath());
-			if (userDirs.length > 1) logError("Found more than one sub-dir for user=" + identity.getName() + " path=" + userDirs[0].getAbsolutePath(), null);
-		}
-	}
-
-	private static FilenameFilter dropboxReturnboxFilter = new FilenameFilter() {
-		@Override
-		public boolean accept(File dir, String name) {
-			// don't add overlayLocales as selectable availableLanguages
-			// (LocaleStrings_de__VENDOR.properties)
-			if (   name.equals(ReturnboxController.RETURNBOX_DIR_NAME) 
-					|| name.equals(DropboxController.DROPBOX_DIR_NAME)) { 
-				return true; 
-			} else {
-				return false;
-			}
-		}
-	};
-
-	/**
-	 * 
-	 * @return e.g. olatdata\bcroot\course\
-	 */
-	private File getCourseBaseContainer() {
-		OlatRootFolderImpl courseRootContainer = new OlatRootFolderImpl(File.separator + PersistingCourseImpl.COURSE_ROOT_DIR_NAME + File.separator, null);
-		return courseRootContainer.getBasefile(); 
-    }
-
 }
 	
 class UserFileFilter implements FilenameFilter  {
diff --git a/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java b/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java
index dbc31ee1095fa5fd779424eb8d49f8ffa2d72b5f..302dd11f6d9c8ddb1b1306a50ceedf05728fa105 100644
--- a/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java
+++ b/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java
@@ -73,12 +73,14 @@ public class VersionMaintenanceForm extends FormBasicController implements Progr
 	private ProgressController progressCtrl;
 	
 	private final VersionsManager versionsManager;
+	private final TaskExecutorManager taskExecutorManager;
 	
 	public VersionMaintenanceForm(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
 		// use combined translator from system admin main
 		setTranslator(Util.createPackageTranslator(SystemAdminMainController.class, ureq.getLocale(), getTranslator()));
 		versionsManager = CoreSpringFactory.getImpl(VersionsManager.class);
+		taskExecutorManager = CoreSpringFactory.getImpl(TaskExecutorManager.class);
 		
 		initForm(ureq);
 	}
@@ -157,7 +159,7 @@ public class VersionMaintenanceForm extends FormBasicController implements Progr
 			confirmPrunehistoryBox = activateYesNoDialog(ureq, null, text, confirmPrunehistoryBox);
 		} else if (source == orphanSize) {
 			orphanSizeEl.setValue(translate("version.orphan.size.calculating"));
-			TaskExecutorManager.getInstance().runTask(new Runnable() {
+			taskExecutorManager.execute(new Runnable() {
 				public void run() {
 					calculateOrphanSize();
 				}
@@ -175,7 +177,7 @@ public class VersionMaintenanceForm extends FormBasicController implements Progr
 		progressCtrl.setMax(100.0f);
 		listenTo(progressCtrl);
 		
-		TaskExecutorManager.getInstance().runTask(new Runnable() {
+		taskExecutorManager.execute(new Runnable() {
 			public void run() {
 				waitASecond();
 				versionsManager.deleteOrphans(VersionMaintenanceForm.this);
@@ -199,7 +201,7 @@ public class VersionMaintenanceForm extends FormBasicController implements Progr
 		progressCtrl.setMax(versionsManager.countDirectories());
 		listenTo(progressCtrl);
 
-		TaskExecutorManager.getInstance().runTask(new Runnable() {
+		taskExecutorManager.execute(new Runnable() {
 			public void run() {
 				waitASecond();
 				Long numOfVersions = getNumOfVersions();
diff --git a/src/main/java/org/olat/core/_spring/mainCorecontext.xml b/src/main/java/org/olat/core/_spring/mainCorecontext.xml
index 08d9399f8a1118ca1e334e3c74d810762be725f3..8e879088cdf84aff7e4e62727c0576d236085893 100644
--- a/src/main/java/org/olat/core/_spring/mainCorecontext.xml
+++ b/src/main/java/org/olat/core/_spring/mainCorecontext.xml
@@ -8,7 +8,7 @@
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context.xsd">
   
-	<context:component-scan base-package="org.olat.core.dispatcher.mapper" />
+	<context:component-scan base-package="org.olat.core.dispatcher.mapper,org.olat.core.commons.taskExecutor.manager" />
 
 	<bean id="coreSpringFactory" class="org.olat.core.CoreSpringFactory" />
 	
diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
index d6b2a7e866c5d9e5703410961bb30be05afb9b92..d5ca284fdc025204553f4ec24db2b084806aa63e 100644
--- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
+++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
@@ -90,6 +90,7 @@
 		<mapping-file>org/olat/upgrade/model/DBMailAttachmentData.hbm.xml</mapping-file>
 		
 		<class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class>
+		<class>org.olat.core.commons.taskExecutor.model.PersistentTask</class>
 		<class>org.olat.group.model.BusinessGroupParticipantViewImpl</class>
 		<class>org.olat.group.model.BusinessGroupOwnerViewImpl</class>
 		<class>org.olat.repository.model.RepositoryEntryLifecycle</class>
diff --git a/src/main/java/org/olat/core/commons/scheduler/_spring/schedulerContext.xml b/src/main/java/org/olat/core/commons/scheduler/_spring/schedulerContext.xml
index 44ec562dd68b17adef20a848963d9e1f263f98f5..0d0c49853f508bd0be61fe467ea8d7c7807dc585 100644
--- a/src/main/java/org/olat/core/commons/scheduler/_spring/schedulerContext.xml
+++ b/src/main/java/org/olat/core/commons/scheduler/_spring/schedulerContext.xml
@@ -43,6 +43,7 @@ How to add a new job:
             <ref bean="invitationCleanupTrigger" />
             <ref bean="epDeadlineTrigger" />
             <ref bean="restTokenTrigger" />
+            <ref bean="taskExecutorTrigger" />
             <ref bean="systemSamplerTrigger"/>
             <ref bean="updateQtiResultsTriggerOnyx"/>
             <ref bean="acReservationCleanupJob"/>
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/LongRunnable.java b/src/main/java/org/olat/core/commons/taskExecutor/LongRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8a40cb8c611c9f507020869e9ca9d492f67a023
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/LongRunnable.java
@@ -0,0 +1,36 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor;
+
+import java.io.Serializable;
+
+/**
+ * 
+ * Marker interface for the task executor manager. The task marked
+ * with this interface will be first persisted to the database and
+ * executed by the scheduler.
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface LongRunnable extends Runnable, Serializable {
+
+}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/TaskExecutorManager.java b/src/main/java/org/olat/core/commons/taskExecutor/TaskExecutorManager.java
index 3bdc88cdb7afab7ca981b436f39459b2c1f25959..24cf49e1510aa101a89b89a4eaf341f77b3af34a 100644
--- a/src/main/java/org/olat/core/commons/taskExecutor/TaskExecutorManager.java
+++ b/src/main/java/org/olat/core/commons/taskExecutor/TaskExecutorManager.java
@@ -25,54 +25,27 @@
 */ 
 package org.olat.core.commons.taskExecutor;
 
-import org.olat.core.logging.AssertException;
-import org.olat.core.manager.BasicManager;
+import java.util.concurrent.Executor;
+
 /**
  * 
  * Description:<br>
  * Generic task executor to run tasks in it's own threads. Use it to decouple stuff that might
- * takes more time than a user may is willing to wait. The task gets executed immediately by a thread pool.
+ * takes more time than a user may is willing to wait. The task gets executed by a thread pool.
+ * Task only marked as Runnable are executed immediately. Task marked by interface LongRunnable
+ * will be persisted to the database and run after some time.
+ * 
  * If you look for scheduled task see @see {@link org.olat.core.commons.scheduler}
  * 
  * <P>
  * Initial Date:  02.05.2007 <br>
  * @author guido
+ * @author srosse, stephane.rosse@frentix.com, http://www.frnetix.com
  */
-public class TaskExecutorManager extends BasicManager {
-	private final ThreadPoolTaskExecutor taskExecutor;
-	
-	private static TaskExecutorManager INSTANCE;
-	
-	/**
-	 * [used by spring]
-	 */
-	private TaskExecutorManager(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
-		this.taskExecutor = threadPoolTaskExecutor;
-		INSTANCE = this;
-	}
+public interface TaskExecutorManager extends Executor {
 	
-	public static TaskExecutorManager getInstance() {
-		return INSTANCE;
-	}
 	
-	public void destroy() {
-		taskExecutor.shutDown();
-	}
-	
-	/**
-	 * runs the task and wraps it in a new runnable to catch uncatched errors
-	 * and may close db sessions used in the task.
-	 * @param task
-	 */
-	public void runTask(final Runnable task) {
-		//wrap call to the task here to catch all errors that are may not catched yet in the task itself
-		//like outOfMemory or other system errors.
-		Task safetask = new Task(task);
-		if (taskExecutor != null) {
-			taskExecutor.runTask(safetask);
-		} else {
-			logError("taskExecutor is not initialized (taskExecutor=null). Do not call 'runTask' before TaskExecutorModule is initialized.", null);
-			throw new AssertException("taskExecutor is not initialized");
-		}
-	}
+	public void executeTaskToDo();
+
+
 }
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/TaskStatus.java b/src/main/java/org/olat/core/commons/taskExecutor/TaskStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..711cd4d975d4a9701057196081e34caab5038089
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/TaskStatus.java
@@ -0,0 +1,33 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum TaskStatus {
+	newTask,
+	inWork,
+	failed,
+	done
+}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/ThreadPoolTaskExecutor.java b/src/main/java/org/olat/core/commons/taskExecutor/ThreadPoolTaskExecutor.java
deleted file mode 100644
index c3ae0cefcbef3c1aa2065fa44487a28e192c3954..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/core/commons/taskExecutor/ThreadPoolTaskExecutor.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <p>
-* Licensed under the Apache License, Version 2.0 (the "License"); <br>
-* you may not use this file except in compliance with the License.<br>
-* You may obtain a copy of the License at
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <p>
-* Unless required by applicable law or agreed to in writing,<br>
-* software distributed under the License is distributed on an "AS IS" BASIS, <br>
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
-* See the License for the specific language governing permissions and <br>
-* limitations under the License.
-* <p>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-package org.olat.core.commons.taskExecutor;
-
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
-import org.springframework.util.StopWatch;
-
-class ThreadPoolTaskExecutor {
-	OLog log = Tracing.createLoggerFor(this.getClass());
-	ThreadPoolExecutor threadPool = null;
-	//The queue all the tasks get filled in and are taken from, if the queue is full the server starts to reject new tasks
-	final ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);
-
-	/**
-	 * @param poolSize
-	 * @param maxPoolSize
-	 * @param keepAliveTime
-	 */
-	public ThreadPoolTaskExecutor(int poolSize, int maxPoolSize, int keepAliveTime) {
-		threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);
-	}
-
-	public void runTask(Runnable task) {
-		StopWatch watch = null;
-		if (log.isDebug()) {
-			watch = new StopWatch();
-			watch.start();
-		}		
-		threadPool.execute(task);
-		
-		if (log.isDebug()) watch.stop();
-		if (log.isDebug()) log.debug("Current size of queue is: "+queue.size()+". Running last task took (ms): "+watch.getTotalTimeMillis());
-	}
-
-	public void shutDown() {
-		// Initiates orderly shutdown and don't accept creation of new threads
-		threadPool.shutdown();
-		if (threadPool.getActiveCount() > 0) {
-			// Stop actively executing threads NOW!
-			List<Runnable> stoppedThreads = threadPool.shutdownNow();
-			for (Runnable runnable : stoppedThreads) {
-				log.info("Shutting down acive thread", runnable.toString());
-			}
-		}
-	}
-
-}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/_spring/taskExecutorCorecontext.xml b/src/main/java/org/olat/core/commons/taskExecutor/_spring/taskExecutorCorecontext.xml
index 8f2f6173410b6bba6d88913c52915aa87b59ef6d..f023ea35f80be53e87863df067d55e26c2a49afb 100644
--- a/src/main/java/org/olat/core/commons/taskExecutor/_spring/taskExecutorCorecontext.xml
+++ b/src/main/java/org/olat/core/commons/taskExecutor/_spring/taskExecutorCorecontext.xml
@@ -3,20 +3,33 @@
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
-  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+  http://www.springframework.org/schema/beans/spring-beans.xsd">
 
-<bean id="taskExecutorManager" class="org.olat.core.commons.taskExecutor.TaskExecutorManager" destroy-method="destroy">
-	<constructor-arg index="0">
-		<bean class="org.olat.core.commons.taskExecutor.ThreadPoolTaskExecutor">
-			<!-- poolSize -->
-			<constructor-arg index="0" value="2" />
-			<!-- maxPoolSize -->
-			<constructor-arg index="1" value="5" />
-			<!-- keepAliveTime -->
-			<constructor-arg index="2" value="10" />
-		</bean>
-	</constructor-arg>
-</bean>
+	<bean id="taskExecutorManager" class="org.olat.core.commons.taskExecutor.manager.TaskExecutorManagerImpl" destroy-method="shutdown">
+		<constructor-arg index="0" ref="taskExecutorService" />
+		<property name="persistentTaskDao" ref="persistentTaskDao"/>
+		<property name="dbInstance" ref="database"/>
+	</bean>
+	
+	<bean id="taskExecutorService" class="org.springframework.core.task.support.ExecutorServiceAdapter">
+		<constructor-arg index="0" ref="taskSpringExecutor" />
+	</bean>
 
+	<bean id="taskSpringExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
+		<property name="corePoolSize" value="2" />
+		<property name="maxPoolSize" value="5" />
+		<property name="queueCapacity" value="1000" />
+	</bean>
+	
+	<!-- Persistent task executor job -->
+	<bean id="taskExecutorTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
+		<property name="jobDetail" ref="taskExecutorJob" />
+  		<!--  every day at 1:21 -->
+		<property name="cronExpression" value="10 */5 * * * ?" />
+		<property name="startDelay" value="60000" />
+	</bean>
+	<bean id="taskExecutorJob" class="org.springframework.scheduling.quartz.JobDetailBean" lazy-init="true">
+		<property name="jobClass" value="org.olat.core.commons.taskExecutor.manager.ExecutorJob" />
+	</bean>
 
 </beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/manager/ExecutorJob.java b/src/main/java/org/olat/core/commons/taskExecutor/manager/ExecutorJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d438b9a00d825a19e83c5aeb53fca5c86a39ed5
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/manager/ExecutorJob.java
@@ -0,0 +1,49 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor.manager;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.scheduler.JobWithDB;
+import org.olat.core.commons.taskExecutor.TaskExecutorManager;
+import org.quartz.JobExecutionContext;
+
+/**
+ * The job execute the persistent tasks
+ * 
+ * 
+ * Initial date: 01.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ExecutorJob extends JobWithDB {
+	
+	/**
+	 * @see org.olat.core.commons.scheduler.JobWithDB#executeWithDB(org.quartz.JobExecutionContext)
+	 */
+	public void executeWithDB(JobExecutionContext context) {
+		try {
+			log.info("Starting checking task to do");
+			CoreSpringFactory.getImpl(TaskExecutorManager.class).executeTaskToDo();
+		} catch (Exception e) {
+			// ups, something went completely wrong! We log this but continue next time
+			log.error("Error while checking task to do", e);
+		}		
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/manager/PersistentTaskDAO.java b/src/main/java/org/olat/core/commons/taskExecutor/manager/PersistentTaskDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..842e0749b5ff46816f0e89ed8b819701e01fe6b1
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/manager/PersistentTaskDAO.java
@@ -0,0 +1,109 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor.manager;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.LockModeType;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.taskExecutor.TaskStatus;
+import org.olat.core.commons.taskExecutor.model.PersistentTask;
+import org.olat.core.util.WebappHelper;
+import org.olat.core.util.xml.XStreamHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.thoughtworks.xstream.XStream;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("persistentTaskDao")
+public class PersistentTaskDAO {
+	
+	private static XStream xstream = XStreamHelper.createXStreamInstance();
+	
+	@Autowired
+	private DB dbInstance;
+
+	public PersistentTask createTask(String name, Serializable task) {
+		PersistentTask ptask = new PersistentTask();
+		Date currentDate = new Date();
+		ptask.setCreationDate(currentDate);
+		ptask.setLastModified(currentDate);
+		ptask.setName(name);
+		ptask.setStatus(TaskStatus.newTask.name());
+		ptask.setTask(xstream.toXML(task));
+		dbInstance.getCurrentEntityManager().persist(ptask);
+		return ptask;
+	}
+	
+	public List<Long> tasksToDo() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select task.key from extask task where task.status='newTask'")
+		  .append(" or (task.status='inWork' and task.executorNode=:executorNode and task.executorBootId!=:executorBootId)");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("executorBootId", WebappHelper.getBootId())
+				.setParameter("executorNode", Integer.toString(WebappHelper.getNodeId()))
+				.getResultList();
+	}
+	
+	public PersistentTask pickTask(Long taskKey) {
+		PersistentTask task = dbInstance.getCurrentEntityManager()
+				.find(PersistentTask.class, taskKey, LockModeType.PESSIMISTIC_WRITE);
+		
+		if(TaskStatus.newTask.name().equals(task.getStatus())) {
+			task.setStatus(TaskStatus.inWork.name());
+			task.setExecutorNode(Integer.toString(WebappHelper.getNodeId()));
+			task.setExecutorBootId(WebappHelper.getBootId());
+			task = dbInstance.getCurrentEntityManager().merge(task);
+		} else if(TaskStatus.inWork.name().equals(task.getStatus())) {
+			task.setExecutorNode(Integer.toString(WebappHelper.getNodeId()));
+			task.setExecutorBootId(WebappHelper.getBootId());
+			task = dbInstance.getCurrentEntityManager().merge(task);
+		}
+		dbInstance.commit();
+		return task;
+	}
+	
+	public void taskDone(PersistentTask task) {
+		task = dbInstance.getCurrentEntityManager().getReference(PersistentTask.class, task.getKey());
+		dbInstance.getCurrentEntityManager().remove(task);
+	}
+	
+	public void taskFailed(PersistentTask task) {
+		task = dbInstance.getCurrentEntityManager()
+				.find(PersistentTask.class, task.getKey(), LockModeType.PESSIMISTIC_WRITE);
+		task.setStatus(TaskStatus.failed.name());
+		dbInstance.commit();
+	}
+	
+	public Runnable deserializeTask(PersistentTask task) {
+		return (Runnable)xstream.fromXML(task.getTask());
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/manager/TaskExecutorManagerImpl.java b/src/main/java/org/olat/core/commons/taskExecutor/manager/TaskExecutorManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8634423c44d98211ffe28078bc2c0f006867ace
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/manager/TaskExecutorManagerImpl.java
@@ -0,0 +1,124 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <p>
+* Licensed under the Apache License, Version 2.0 (the "License"); <br>
+* you may not use this file except in compliance with the License.<br>
+* You may obtain a copy of the License at
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <p>
+* Unless required by applicable law or agreed to in writing,<br>
+* software distributed under the License is distributed on an "AS IS" BASIS, <br>
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+* See the License for the specific language governing permissions and <br>
+* limitations under the License.
+* <p>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.  
+* <p>
+*/ 
+package org.olat.core.commons.taskExecutor.manager;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.taskExecutor.LongRunnable;
+import org.olat.core.commons.taskExecutor.TaskExecutorManager;
+import org.olat.core.commons.taskExecutor.model.DBSecureRunnable;
+import org.olat.core.commons.taskExecutor.model.PersistentTaskRunnable;
+import org.olat.core.logging.AssertException;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.manager.BasicManager;
+
+/**
+ * 
+ * Description:<br>
+ * Generic task executor to run tasks in it's own threads. Use it to decouple stuff that might
+ * takes more time than a user may is willing to wait. The task gets executed by a thread pool.
+ * If you look for scheduled task see @see {@link org.olat.core.commons.scheduler}
+ * 
+ * <P>
+ * Initial Date:  02.05.2007 <br>
+ * @author guido
+ * @author srosse, stephane.rosse@frentix.com, http://www.frnetix.com
+ */
+public class TaskExecutorManagerImpl extends BasicManager implements TaskExecutorManager {
+	private static final OLog log = Tracing.createLoggerFor(TaskExecutorManagerImpl.class);
+	private final ExecutorService taskExecutor;
+	
+	private DB dbInstance;
+	private PersistentTaskDAO persistentTaskDao;
+
+	/**
+	 * [used by spring]
+	 */
+	private TaskExecutorManagerImpl(ExecutorService threadPoolTaskExecutor) {
+		this.taskExecutor = threadPoolTaskExecutor;
+	}
+	
+	/**
+	 * [used by Spring]
+	 * @param dbInstance
+	 */
+	public void setDbInstance(DB dbInstance) {
+		this.dbInstance = dbInstance;
+	}
+
+	/**
+	 * [used by Spring]
+	 * @param persistentTaskDao
+	 */
+	public void setPersistentTaskDao(PersistentTaskDAO persistentTaskDao) {
+		this.persistentTaskDao = persistentTaskDao;
+	}
+
+	public void shutdown() {
+		taskExecutor.shutdown();
+	}
+	
+	@Override
+	public void execute(Runnable task) {
+		//wrap call to the task here to catch all errors that are may not catched yet in the task itself
+		//like outOfMemory or other system errors.
+		
+		if(task instanceof LongRunnable) {
+			persistentTaskDao.createTask(UUID.randomUUID().toString(), (LongRunnable)task);
+			dbInstance.commit();
+		} else {
+			if (taskExecutor != null) {
+				DBSecureRunnable safetask = new DBSecureRunnable(task);
+				taskExecutor.submit(safetask);
+			} else {
+				logError("taskExecutor is not initialized (taskExecutor=null). Do not call 'runTask' before TaskExecutorModule is initialized.", null);
+				throw new AssertException("taskExecutor is not initialized");
+			}
+		}
+	}
+
+	@Override
+	public void executeTaskToDo() {
+		try {
+			PersistentTaskDAO taskDao = CoreSpringFactory.getImpl(PersistentTaskDAO.class);
+			TaskExecutorManager executor = CoreSpringFactory.getImpl(TaskExecutorManager.class);
+			
+			List<Long> todos = taskDao.tasksToDo();
+			for(Long todo:todos) {
+				PersistentTaskRunnable command = new PersistentTaskRunnable(todo);
+				executor.execute(command);
+			}
+		} catch (Exception e) {
+			// ups, something went completely wrong! We log this but continue next time
+			log.error("Error while executing task todo", e);
+		}		
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/Task.java b/src/main/java/org/olat/core/commons/taskExecutor/model/DBSecureRunnable.java
similarity index 79%
rename from src/main/java/org/olat/core/commons/taskExecutor/Task.java
rename to src/main/java/org/olat/core/commons/taskExecutor/model/DBSecureRunnable.java
index d647ace69e9c357abaa266e9930bf7b02388e8ec..e71c5240c201556cbceadc127ce1a851def1e447 100644
--- a/src/main/java/org/olat/core/commons/taskExecutor/Task.java
+++ b/src/main/java/org/olat/core/commons/taskExecutor/model/DBSecureRunnable.java
@@ -17,20 +17,22 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.core.commons.taskExecutor;
+package org.olat.core.commons.taskExecutor.model;
 
 import org.olat.core.commons.persistence.DBFactory;
+import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 
 /**
  * 
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class Task implements Runnable {
+public class DBSecureRunnable implements Runnable {
 	
+	private static final OLog log = Tracing.createLoggerFor(DBSecureRunnable.class);
 	private final Runnable task;
 	
-	public Task(Runnable task) {
+	public DBSecureRunnable(Runnable task) {
 		this.task = task;
 	}
 
@@ -41,7 +43,7 @@ public class Task implements Runnable {
 			DBFactory.getInstance().commitAndCloseSession();
 		} catch (Throwable e) {
 			DBFactory.getInstance().rollbackAndCloseSession();
-			Tracing.logError("Error while running task in a separate thread.", e, TaskExecutorManager.class);
+			log.error("Error while running task in a separate thread.", e);
 		}
 	}
 }
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTask.java b/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d643620200d1def16e2094c83a7f34b505d5138
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTask.java
@@ -0,0 +1,179 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="extask")
+@Table(name="o_ex_task")
+@NamedQueries({
+	@NamedQuery(name="loadTaskByKey", query="select task from extask where task.key=:taskKey")
+})
+public class PersistentTask implements CreateInfo, ModifiedInfo, Persistable {
+	
+	private static final long serialVersionUID = 800884851125711998L;
+
+	@Id
+  @GeneratedValue(generator = "system-uuid")
+  @GenericGenerator(name = "system-uuid", strategy = "hilo")
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="e_name", nullable=false, insertable=true, updatable=false)
+	private String name;
+	
+	@Column(name="e_status", nullable=false, insertable=true, updatable=true)
+	private String status;
+	
+	@Column(name="e_executor_node", nullable=true, insertable=true, updatable=true)
+	private String executorNode;
+	
+	@Column(name="e_executor_boot_id", nullable=true, insertable=true, updatable=true)
+	private String executorBootId;
+	
+	@Column(name="e_task", nullable=false, insertable=true, updatable=false)
+	private String task;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getStatus() {
+		return status;
+	}
+
+	public void setStatus(String status) {
+		this.status = status;
+	}
+
+	public String getExecutorNode() {
+		return executorNode;
+	}
+
+	public void setExecutorNode(String executorNode) {
+		this.executorNode = executorNode;
+	}
+
+	public String getExecutorBootId() {
+		return executorBootId;
+	}
+
+	public void setExecutorBootId(String executorBootId) {
+		this.executorBootId = executorBootId;
+	}
+
+	public String getTask() {
+		return task;
+	}
+
+	public void setTask(String task) {
+		this.task = task;
+	}
+	
+	@Override
+	public int hashCode() {
+		return key == null ? -3987254 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof PersistentTask) {
+			PersistentTask q = (PersistentTask)obj;
+			return key != null && key.equals(q.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("extask[key=").append(this.key)
+			.append("]").append(super.toString());
+		return sb.toString();
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTaskRunnable.java b/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTaskRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb8c8440ead6174b908e8619d5c5c99a15f97e4b
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/taskExecutor/model/PersistentTaskRunnable.java
@@ -0,0 +1,68 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor.model;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.persistence.DBFactory;
+import org.olat.core.commons.taskExecutor.manager.PersistentTaskDAO;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class PersistentTaskRunnable implements Runnable {
+	
+	private static final OLog log = Tracing.createLoggerFor(PersistentTaskRunnable.class);
+	private final Long taskKey;
+	
+	public PersistentTaskRunnable(Long taskKey) {
+		this.taskKey = taskKey;
+	}
+
+	@Override
+	public void run() {
+		PersistentTask task = null;
+		PersistentTaskDAO taskDao = CoreSpringFactory.getImpl(PersistentTaskDAO.class);
+		try {
+			task = taskDao.pickTask(taskKey);
+			Runnable runnable = taskDao.deserializeTask(task);
+			runnable.run();
+			taskDao.taskDone(task);
+			DBFactory.getInstance().commitAndCloseSession();
+		} catch (Throwable e) {
+			DBFactory.getInstance().rollbackAndCloseSession();
+			markAsFailed(task);
+			log.error("Error while running task in a separate thread: " + task.getKey(), e);
+		}
+	}
+	
+	private void markAsFailed(PersistentTask task) {
+		if(task == null) return;
+		try {
+			PersistentTaskDAO taskDao = CoreSpringFactory.getImpl(PersistentTaskDAO.class);
+			taskDao.taskFailed(task);
+			DBFactory.getInstance().commitAndCloseSession();
+		} catch (Exception e1) {
+			DBFactory.getInstance().rollbackAndCloseSession();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/core/util/WebappHelper.java b/src/main/java/org/olat/core/util/WebappHelper.java
index 6fb40bf0a0bb365f64748841534a19811d054262..96491a1cf093036a5c6e67ea1c44c746580e04fc 100644
--- a/src/main/java/org/olat/core/util/WebappHelper.java
+++ b/src/main/java/org/olat/core/util/WebappHelper.java
@@ -35,6 +35,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.UUID;
 
 import javax.servlet.ServletContext;
 
@@ -62,6 +63,7 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 	
 	private static final OLog log = Tracing.createLoggerFor(WebappHelper.class);
 	private static int nodeId;
+	private static final String bootId = UUID.randomUUID().toString();
 	private static String fullPathToSrc;
 	private static String fullPathToWebappSrc;
 	private static ServletContext servletContext;
@@ -148,6 +150,13 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA
 	public static int getNodeId() {
 		return nodeId;
 	}
+	
+	/**
+	 * @return Return a unique ID per node and per reboot
+	 */
+	public static String getBootId() {
+		return bootId;
+	}
 
 	/**
 	 * implements service interface
diff --git a/src/main/java/org/olat/course/assessment/AssessmentModule.java b/src/main/java/org/olat/course/assessment/AssessmentModule.java
index bb2904fc29d1272842aceb28b721dab638dfb97f..8f06d1c1e8edae0ab85ce82d43df06bbaaeaf4d4 100644
--- a/src/main/java/org/olat/course/assessment/AssessmentModule.java
+++ b/src/main/java/org/olat/course/assessment/AssessmentModule.java
@@ -30,6 +30,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.taskExecutor.TaskExecutorManager;
 import org.olat.core.configuration.Destroyable;
 import org.olat.core.configuration.Initializable;
@@ -56,6 +57,7 @@ public class AssessmentModule implements Initializable, Destroyable, GenericEven
 
 	private List<Long> upcomingWork;
 	private CourseModule courseModule;
+	private TaskExecutorManager taskExecutorManager;
 	
 	/**
 	 * [used by spring]
@@ -63,6 +65,14 @@ public class AssessmentModule implements Initializable, Destroyable, GenericEven
 	private AssessmentModule(CourseModule courseModule) {
 		this.courseModule = courseModule;
 	}
+	
+	/**
+	 * [user by Spring]
+	 * @param taskExecutorManager
+	 */
+	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
+		this.taskExecutorManager = taskExecutorManager;
+	}
 
 	/**
 	 * @see org.olat.core.configuration.OLATModule#init(com.anthonyeden.lib.config.Configuration)
@@ -136,7 +146,7 @@ public class AssessmentModule implements Initializable, Destroyable, GenericEven
 		if (recalc) {
 			ICourse pubCourse = CourseFactory.loadCourse(resId);
 			UpdateEfficiencyStatementsWorker worker = new UpdateEfficiencyStatementsWorker(pubCourse);
-			TaskExecutorManager.getInstance().runTask(worker);
+			taskExecutorManager.execute(worker);
 		}
 	}
 
@@ -151,14 +161,14 @@ public class AssessmentModule implements Initializable, Destroyable, GenericEven
 			return;
 		}
 		// deleted + inserted + modified node ids -> changedNodeIds
-		Set changedNodeIds = pe.getDeletedCourseNodeIds();
+		Set<String> changedNodeIds = pe.getDeletedCourseNodeIds();
 		changedNodeIds.addAll(pe.getInsertedCourseNodeIds());
 		changedNodeIds.addAll(pe.getModifiedCourseNodeIds());
 		//
 		boolean courseAssessmentChanged = false;
 		Structure courseRun = course.getRunStructure();
-		for (Iterator iter = changedNodeIds.iterator(); iter.hasNext();) {
-			String nodeId = (String) iter.next();
+		for (Iterator<String> iter = changedNodeIds.iterator(); iter.hasNext();) {
+			String nodeId = iter.next();
 			boolean wasNodeAsessable = AssessmentHelper.checkIfNodeIsAssessable(courseRun.getNode(nodeId));
 			boolean isNodeAssessable = AssessmentHelper.checkIfNodeIsAssessable(course.getEditorTreeModel().getCourseNode(nodeId));
 			//if node was or became assessable
diff --git a/src/main/java/org/olat/course/assessment/_spring/assessmentContext.xml b/src/main/java/org/olat/course/assessment/_spring/assessmentContext.xml
index eefed18b2da447bd484a7b8b2e2ca1b9b51e79b5..39b5e21a35b05627f2cfb7a4a937b7d1091e22fb 100644
--- a/src/main/java/org/olat/course/assessment/_spring/assessmentContext.xml
+++ b/src/main/java/org/olat/course/assessment/_spring/assessmentContext.xml
@@ -12,6 +12,7 @@
 
 	<bean id="assessmentModule" class="org.olat.course.assessment.AssessmentModule" init-method="init" destroy-method="destroy" >
 		<constructor-arg index="0" ref="courseModule" />
+		<property name="taskExecutorManager" ref="taskExecutorManager"/>
 	</bean>
 
 	<bean id="org.olat.course.assessment.EfficiencyStatementManager" class="org.olat.course.assessment.EfficiencyStatementManager">
diff --git a/src/main/java/org/olat/course/editor/PublishEvent.java b/src/main/java/org/olat/course/editor/PublishEvent.java
index abd8bd94a7c055c517957531885a0b9620a1a010..78e17aefd306b4949bb98713391ff6da128d3ccd 100644
--- a/src/main/java/org/olat/course/editor/PublishEvent.java
+++ b/src/main/java/org/olat/course/editor/PublishEvent.java
@@ -35,6 +35,8 @@ import org.olat.course.ICourse;
 * @author Felix Jost
 */
 public class PublishEvent extends MultiUserEvent {
+
+	private static final long serialVersionUID = 7105017036750676773L;
 	public final static int PRE_PUBLISH =0;
 	public final static int PUBLISH = 1;
 	//TODO: LD: temporary introduced, for the purpose of identifying the source of the event (same VM or another cluster node)
@@ -44,9 +46,9 @@ public class PublishEvent extends MultiUserEvent {
 	long pubtimestamp;
 	private Long publishedCourseResId;
 	
-	private Set insertedCourseNodeIds;
-	private Set deletedCourseNodeIds;
-	private Set modifiedCourseNodeIds;
+	private Set<String> insertedCourseNodeIds;
+	private Set<String> deletedCourseNodeIds;
+	private Set<String> modifiedCourseNodeIds;
 
 	private int state = PUBLISH;
 	
@@ -74,15 +76,15 @@ public class PublishEvent extends MultiUserEvent {
 		return pubtimestamp;
 	}
 	
-	public Set getInsertedCourseNodeIds() {
+	public Set<String> getInsertedCourseNodeIds() {
 		return insertedCourseNodeIds;
 	}
 
-	public Set getDeletedCourseNodeIds() {
+	public Set<String> getDeletedCourseNodeIds() {
 		return deletedCourseNodeIds;
 	}
 
-	public Set getModifiedCourseNodeIds() {
+	public Set<String> getModifiedCourseNodeIds() {
 		return modifiedCourseNodeIds;
 	}
 	
diff --git a/src/main/java/org/olat/course/statistic/AsyncExportManager.java b/src/main/java/org/olat/course/statistic/AsyncExportManager.java
index d54ae59183f2df27df026694f5adb37233021b75..6741a88d436802d6e829dbf71bcc7574b63ae6c5 100644
--- a/src/main/java/org/olat/course/statistic/AsyncExportManager.java
+++ b/src/main/java/org/olat/course/statistic/AsyncExportManager.java
@@ -52,6 +52,8 @@ public class AsyncExportManager extends BasicManager {
 
 	/** set via spring **/
 	private int concurrentExportsPerNode_ = 2;
+	
+	private TaskExecutorManager taskExecutorManager;
 
 	/** the identities currently executing an export **/
 	private final Set<Identity> identitiesOfJobsCurrentlyRunning_ = new HashSet<Identity>();
@@ -65,6 +67,14 @@ public class AsyncExportManager extends BasicManager {
 		INSTANCE = this;
 	}
 	
+	/**
+	 * [used by Spring]
+	 * @param taskExecutorManager
+	 */
+	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
+		this.taskExecutorManager = taskExecutorManager;
+	}
+
 	/**
 	 * @return Singleton.
 	 */
@@ -95,7 +105,7 @@ public class AsyncExportManager extends BasicManager {
 			log_.info("asyncArchiveCourseLogFiles: user "+identity.getName()+" wants to archive a course log. Already pending jobs: "+waitingCnt_);
 		}
 		
-		TaskExecutorManager.getInstance().runTask(new Runnable() {
+		taskExecutorManager.execute(new Runnable() {
 
 			@Override
 			public void run() {
diff --git a/src/main/java/org/olat/course/statistic/StatisticUpdateManagerImpl.java b/src/main/java/org/olat/course/statistic/StatisticUpdateManagerImpl.java
index 5dec796b1796a793feb7feb297301fd0e872693f..c4e94481cbced2cce5272989857ed36abded826e 100644
--- a/src/main/java/org/olat/course/statistic/StatisticUpdateManagerImpl.java
+++ b/src/main/java/org/olat/course/statistic/StatisticUpdateManagerImpl.java
@@ -74,6 +74,7 @@ class StatisticUpdateManagerImpl extends BasicManager implements StatisticUpdate
 	
 	boolean updateOngoing_ = false;
 	
+	private TaskExecutorManager taskExecutorManager;
 	
 	/** spring **/
 	public StatisticUpdateManagerImpl(CoordinatorManager coordinatorManager, StatisticUpdateConfig config, String enabled) {
@@ -95,6 +96,13 @@ class StatisticUpdateManagerImpl extends BasicManager implements StatisticUpdate
 				OresHelper.createOLATResourceableTypeWithoutCheck(StatisticUpdateManagerImpl.class.getName()));
 	}
 	
+	/**
+	 * [used by Spring]
+	 * @param taskExecutorManager
+	 */
+	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
+		this.taskExecutorManager = taskExecutorManager;
+	}
 
 	@Override
 	public void addStatisticUpdater(IStatisticUpdater updater) {
@@ -169,7 +177,7 @@ class StatisticUpdateManagerImpl extends BasicManager implements StatisticUpdate
 			
 		};
 		try{
-			TaskExecutorManager.getInstance().runTask(r);
+			taskExecutorManager.execute(r);
 			log_.info("updateStatistics: starting the update in its own thread");
 			return true;
 		} catch(AssertException ae) {
diff --git a/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml b/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml
index d4b9e0b72d0c77ffc7d9bde844efda3ed1a8f987..71c3afa3c210d63baba405aee90c653cc03dabe2 100644
--- a/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml
+++ b/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml
@@ -1,12 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xmlns:context="http://www.springframework.org/schema/context" 
 	xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
-  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
-  http://www.springframework.org/schema/context 
-  http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+  http://www.springframework.org/schema/beans/spring-beans.xsd">
 
 	<!-- 
 		
@@ -81,7 +78,7 @@
 	
 	<bean id="asyncLogExportManager"
 		class="org.olat.course.statistic.AsyncExportManager">
-		
+		<property name="taskExecutorManager" ref="taskExecutorManager"/>
 		<property name="concurrentExportsPerNode" value="2"/>
 	</bean>
 
@@ -671,6 +668,7 @@
 			<ref bean="statisticUpdateConfig_${db.vendor}"/>
 		</constructor-arg>
 		<constructor-arg value="${cluster.singleton.services}"/>
+		<property name="taskExecutorManager" ref="taskExecutorManager"/>
 	</bean>
 
 	<bean class="org.olat.course.statistic.SimpleStatisticInfoHelper" >
diff --git a/src/main/java/org/olat/group/ui/BusinessGroupModuleAdminController.java b/src/main/java/org/olat/group/ui/BusinessGroupModuleAdminController.java
index 3bad85b4275357ef3dedcda8fd59a72bb10c788a..a22ced417aa8306aeff3e3c46a34cd0180746df4 100644
--- a/src/main/java/org/olat/group/ui/BusinessGroupModuleAdminController.java
+++ b/src/main/java/org/olat/group/ui/BusinessGroupModuleAdminController.java
@@ -253,7 +253,7 @@ public class BusinessGroupModuleAdminController extends FormBasicController impl
 				businessGroupService.dedupMembers(getIdentity(), coaches, participants, BusinessGroupModuleAdminController.this);
 			}
 		};
-		TaskExecutorManager.getInstance().runTask(worker);
+		CoreSpringFactory.getImpl(TaskExecutorManager.class).execute(worker);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java b/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java
index 671e0b4a91850b5e25b76a7cbe8b6421fddb2a2c..ae5fbac12d154f7c37b6588ae312e418cac72b28 100644
--- a/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java
+++ b/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java
@@ -167,7 +167,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve
 				doBatchSync(errors);
 			}				
 		};
-		taskExecutorManager.runTask(batchSyncTask);		
+		taskExecutorManager.execute(batchSyncTask);		
 	}
 
 	/**
diff --git a/src/main/java/org/olat/search/_spring/searchContext.xml b/src/main/java/org/olat/search/_spring/searchContext.xml
index ba48be20413a95205e21e520f501e9fbf39dc61a..a7c2d3c0324317ddf42bb917a28c814c2e8cb5d5 100644
--- a/src/main/java/org/olat/search/_spring/searchContext.xml
+++ b/src/main/java/org/olat/search/_spring/searchContext.xml
@@ -72,6 +72,7 @@
 		<property name="connectionFactory" ref="searchConnectionFactory"/>
 		<property name="searchQueue" ref="searchQueue"/>
 		<property name="receiveTimeout" value="45000"/>
+		<property name="taskExecutorManager" ref="taskExecutorManager"/>
 	</bean>
 	
 	<bean id="fileDocumentFactory" class="org.olat.search.service.document.file.FileDocumentFactory">
diff --git a/src/main/java/org/olat/search/service/searcher/JmsSearchProvider.java b/src/main/java/org/olat/search/service/searcher/JmsSearchProvider.java
index 7e531fa88d37f477f1c0b59c1b1fc653f504630a..dc2a609b9864dfe96588b384bfc5de1d846b6cfd 100644
--- a/src/main/java/org/olat/search/service/searcher/JmsSearchProvider.java
+++ b/src/main/java/org/olat/search/service/searcher/JmsSearchProvider.java
@@ -77,6 +77,7 @@ public class JmsSearchProvider implements MessageListener {
 	private MessageConsumer consumer_;
 	private LinkedList<Session> sessions_ = new LinkedList<Session>();
 	private long receiveTimeout = 60000;
+	private TaskExecutorManager taskExecutorManager;
 	
 	/**
 	 * [used by spring]
@@ -98,6 +99,10 @@ public class JmsSearchProvider implements MessageListener {
 		this.receiveTimeout = receiveTimeout;
 	}
 
+	public void setTaskExecutorManager(TaskExecutorManager taskExecutorManager) {
+		this.taskExecutorManager = taskExecutorManager;
+	}
+
 	/**
 	 * Delegates execution to the searchService.
 	 * @see org.olat.search.service.searcher.OLATSearcher#doSearch(java.lang.String, org.olat.core.id.Identity, org.olat.core.id.Roles, boolean)
@@ -171,7 +176,7 @@ public class JmsSearchProvider implements MessageListener {
 				if (message instanceof ObjectMessage) {
 					ObjectMessage objectMessage = (ObjectMessage) message;
 					final SearchRequest searchRequest = (SearchRequest) objectMessage.getObject();
-					TaskExecutorManager.getInstance().runTask(new Runnable() {
+					taskExecutorManager.execute(new Runnable() {
 		
 						public void run() {
 							onSearchMessage(searchRequest, correlationID, replyTo);
@@ -181,7 +186,7 @@ public class JmsSearchProvider implements MessageListener {
 				} else if (message instanceof TextMessage) {				
 					TextMessage testMessage = (TextMessage)message;
 					final String spellText = testMessage.getText();
-					TaskExecutorManager.getInstance().runTask(new Runnable() {
+					taskExecutorManager.execute(new Runnable() {
 		
 						public void run() {
 							onSpellMessage(spellText, correlationID, replyTo);
diff --git a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql
index 1bf9d70456b9d6c06d20576125faf2136f4d59ad..b4bc1e6213d66a0aa52a7193c5fef0c24323edec 100644
--- a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql
+++ b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql
@@ -392,7 +392,6 @@ alter table o_gp_business add column external_id varchar(64);
 alter table o_gp_business add column managed_flags varchar(255);
 create index idx_grp_lifecycle_soft_idx on o_gp_business (external_id);
 
-
 -- complet missing index
 
 create index idx_ident_creationdate_idx on o_bs_identity (creationdate);
@@ -401,5 +400,19 @@ create index idx_id_lastlogin_idx on o_bs_identity (lastlogin);
 create index idx_policy_grp_rsrc_idx on o_bs_policy (oresource_id, group_id);
 
 
+-- task executor
+create table o_ex_task (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   e_name varchar(255) not null,
+   e_status varchar(16) not null,
+   e_executor_node varchar(16),
+   e_executor_boot_id varchar(64),
+   e_task mediumtext not null,
+   primary key (id)
+);
+alter table o_ex_task ENGINE = InnoDB;
+
 
 
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index e61c9aeef75ef1f3b2ed01ecdfdb2cdd3c772b44..6b3a7311321195a9d98914573eef5e6589f2d900 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1235,6 +1235,18 @@ create table o_lti_outcome (
    primary key (id)
 );
 
+create table o_ex_task (
+   id bigint not null,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   e_name varchar(255) not null,
+   e_status varchar(16) not null,
+   e_executor_node varchar(16),
+   e_executor_boot_id varchar(64),
+   e_task mediumtext not null,
+   primary key (id)
+);
+
 
 -- user view
 create view o_bs_identity_short_v as (
@@ -1885,7 +1897,7 @@ alter table o_im_message ENGINE = InnoDB;
 alter table o_im_notification ENGINE = InnoDB;
 alter table o_im_roster_entry ENGINE = InnoDB;
 alter table o_im_preferences ENGINE = InnoDB;
-
+alter table o_ex_task ENGINE = InnoDB;
 
 
 -- rating
diff --git a/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql
index 88efb30b6df91ff18f3fc659e2845f9fff6bee86..3e80bace5f7c9b10a1a072f89a235233a83d36b2 100644
--- a/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql
+++ b/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql
@@ -482,6 +482,20 @@ create index idx_ucourseinfos_ident_idx on o_as_user_course_infos (fk_identity);
 create index idx_ucourseinfos_rsrc_idx on o_as_user_course_infos (fk_resource_id);
 
 
+-- task executor
+create table o_ex_task (
+   id number(20) not null,
+   creationdate date not null,
+   lastmodified date not null,
+   e_name varchar2(255 char) not null,
+   e_status varchar2(16 char) not null,
+   e_executor_node varchar2(16 char),
+   e_executor_boot_id varchar2(64 char),
+   e_task clob not null,
+   primary key (id)
+);
+
+
 
 
 
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 3f8f7329d9bb13dbdd5860cc977c9085f1372c7f..034da1c876eb9ca5c946b7ecc47f1fc0ead3c233 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1278,6 +1278,18 @@ create table o_lti_outcome (
    primary key (id)
 );
 
+create table o_ex_task (
+   id number(20) not null,
+   creationdate date not null,
+   lastmodified date not null,
+   e_name varchar2(255 char) not null,
+   e_status varchar2(16 char) not null,
+   e_executor_node varchar2(16 char),
+   e_executor_boot_id varchar2(64 char),
+   e_task clob not null,
+   primary key (id)
+);
+
 --
 -- Table: o_co_db_entry
 --;
diff --git a/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql
index c54c9c28b6bea5c1cf52e1692acbececfa0b8e93..e239f941216e36bdddf3bdc930051415c3f61db0 100644
--- a/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql
+++ b/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql
@@ -537,7 +537,18 @@ create index idx_ucourseinfos_rsrc_idx on o_as_user_course_infos (fk_resource_id
 -- course infos
 alter table o_as_user_course_infos add unique (fk_identity, fk_resource_id);
 
-
+-- task executor
+create table o_ex_task (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   e_name varchar(255) not null,
+   e_status varchar(16) not null,
+   e_executor_node varchar(16),
+   e_executor_boot_id varchar(64),
+   e_task text not null,
+   primary key (id)
+);
 
 
 
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 64a1750f6075c553e5913ba64162ada0904d224c..26d3632ff7c5899e7cb8fc4fe186e1f3db22d562 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1232,6 +1232,18 @@ create table o_lti_outcome (
    primary key (id)
 );
 
+create table o_ex_task (
+   id int8 not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   e_name varchar(255) not null,
+   e_status varchar(16) not null,
+   e_executor_node varchar(16),
+   e_executor_boot_id varchar(64),
+   e_task text not null,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
diff --git a/src/test/java/org/olat/core/commons/taskExecutor/PersistentTaskDAOTest.java b/src/test/java/org/olat/core/commons/taskExecutor/PersistentTaskDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..21b9525a4e4133a28801b470c134a2ebddff2ccb
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/taskExecutor/PersistentTaskDAOTest.java
@@ -0,0 +1,159 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.taskExecutor.manager.PersistentTaskDAO;
+import org.olat.core.commons.taskExecutor.model.PersistentTask;
+import org.olat.core.util.WebappHelper;
+import org.olat.core.util.xml.XStreamHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class PersistentTaskDAOTest extends OlatTestCase  {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private PersistentTaskDAO persistentTaskDao;
+	
+	
+	@Test
+	public void testCreateTask() {
+		String name = "Task 0";
+		PersistentTask task = persistentTaskDao.createTask(name, new DummyTask());
+		
+		dbInstance.commit();
+		
+		Assert.assertNotNull(task);
+		Assert.assertNotNull(task.getKey());
+		Assert.assertNotNull(task.getCreationDate());
+		Assert.assertNotNull(task.getLastModified());
+		Assert.assertNotNull(task.getTask());
+		Assert.assertEquals(TaskStatus.newTask.name(), task.getStatus());
+	}
+
+	@Test
+	public void testPickTask() {
+		String name = "Task 1";
+		PersistentTask task = persistentTaskDao.createTask(name, new DummyTask());
+		dbInstance.commitAndCloseSession();
+		
+		PersistentTask todo = persistentTaskDao.pickTask(task.getKey());
+
+		Assert.assertNotNull(todo);
+		Assert.assertEquals(task.getKey(), todo.getKey());
+		Assert.assertEquals(TaskStatus.inWork.name(), todo.getStatus());
+	}
+	
+	@Test
+	public void testTodo() {
+		String name = UUID.randomUUID().toString();
+		PersistentTask task = persistentTaskDao.createTask(name, new DummyTask());
+		dbInstance.commitAndCloseSession();
+		
+		List<Long> todos = persistentTaskDao.tasksToDo();
+
+		Assert.assertNotNull(todos);
+		Assert.assertTrue(todos.contains(task.getKey()));
+	}
+	
+	@Test
+	public void testTodo_workflow() {
+		String name = UUID.randomUUID().toString();
+		persistentTaskDao.createTask(name, new DummyTask());
+		dbInstance.commitAndCloseSession();
+		
+		int count = 0;
+		List<Long> todos = persistentTaskDao.tasksToDo();
+		for(Long todo:todos) {
+			PersistentTask taskToDo = persistentTaskDao.pickTask(todo);
+			persistentTaskDao.taskDone(taskToDo);
+			count++;
+		}
+		dbInstance.commitAndCloseSession();
+		Assert.assertTrue(count > 0);
+		
+		List<Long> nothingTodos = persistentTaskDao.tasksToDo();
+		Assert.assertNotNull(nothingTodos);
+		Assert.assertTrue(nothingTodos.isEmpty());
+	}
+	
+	@Test
+	public void testTodo_oldTasks() {
+		String name = UUID.randomUUID().toString();
+		PersistentTask ctask = persistentTaskDao.createTask(name, new DummyTask());
+		
+		//simulate a task from a previous boot
+		PersistentTask ptask = new PersistentTask();
+		ptask.setCreationDate(new Date());
+		ptask.setLastModified(new Date());
+		ptask.setName(UUID.randomUUID().toString());
+		ptask.setStatus(TaskStatus.inWork.name());
+		ptask.setExecutorBootId(UUID.randomUUID().toString());
+		ptask.setExecutorNode(Integer.toString(WebappHelper.getNodeId()));
+		ptask.setTask(XStreamHelper.createXStreamInstance().toXML(new DummyTask()));
+		dbInstance.getCurrentEntityManager().persist(ptask);
+
+		//simulate a task from an other node
+		PersistentTask alienTask = new PersistentTask();
+		alienTask.setCreationDate(new Date());
+		alienTask.setLastModified(new Date());
+		alienTask.setName(UUID.randomUUID().toString());
+		alienTask.setStatus(TaskStatus.inWork.name());
+		alienTask.setExecutorBootId(UUID.randomUUID().toString());
+		alienTask.setExecutorNode(Integer.toString(WebappHelper.getNodeId() + 1));
+		alienTask.setTask(XStreamHelper.createXStreamInstance().toXML(new DummyTask()));
+		dbInstance.getCurrentEntityManager().persist(alienTask);
+
+		dbInstance.commitAndCloseSession();
+
+		List<Long> todos = persistentTaskDao.tasksToDo();
+		Assert.assertNotNull(todos);
+		Assert.assertFalse(todos.isEmpty());
+		Assert.assertTrue(todos.contains(ptask.getKey()));
+		Assert.assertTrue(todos.contains(ctask.getKey()));
+		Assert.assertFalse(todos.contains(alienTask.getKey()));
+	}
+	
+	
+	public static class DummyTask implements Runnable, Serializable {
+		private static final long serialVersionUID = 5193785402425324970L;
+
+		@Override
+		public void run() {
+			//
+		}
+	}
+}
diff --git a/src/test/java/org/olat/core/commons/taskExecutor/TaskExecutorManagerTest.java b/src/test/java/org/olat/core/commons/taskExecutor/TaskExecutorManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..274abefdc24fc9fc927212ffdcf2a3751f6c5672
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/taskExecutor/TaskExecutorManagerTest.java
@@ -0,0 +1,73 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.taskExecutor;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 02.07.2013<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class TaskExecutorManagerTest extends OlatTestCase {
+	
+	private static final OLog log = Tracing.createLoggerFor(TaskExecutorManagerTest.class);
+	
+	@Autowired
+	private TaskExecutorManager taskExecutorManager;
+	
+	@Test
+	public void testRunTask() {
+		final CountDownLatch finishCount = new CountDownLatch(1);
+		taskExecutorManager.execute(new DummyTask(finishCount));
+		
+		try {
+			boolean zero = finishCount.await(10, TimeUnit.SECONDS);
+			Assert.assertTrue(zero);
+		} catch (InterruptedException e) {
+			log.error("", e);
+			Assert.fail();
+		}
+	}
+	
+	public static class DummyTask implements Runnable {
+		
+		private final CountDownLatch finishCount;
+		
+		public DummyTask(CountDownLatch finishCount) {
+			this.finishCount = finishCount;
+		}
+		
+		@Override
+		public void run() {
+			finishCount.countDown();
+		}
+	}
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 353c3f0cdc9ac88f640362595d76870fe38eff2e..b9c5608981a2d8c6de852d57ece56537815c590b 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -78,6 +78,9 @@ import org.junit.runners.Suite;
 	org.olat.commons.coordinate.cluster.lock.LockTest.class,//ok
 	org.olat.commons.coordinate.CoordinatorTest.class,//ok
 	org.olat.core.commons.service.webdav.WebDAVTestCase.class,//ok
+	org.olat.core.commons.services.webdav.manager.WebDAVManagerTest.class,//ok
+	org.olat.core.commons.taskExecutor.PersistentTaskDAOTest.class,//ok
+	org.olat.core.commons.taskExecutor.TaskExecutorManagerTest.class,//ok
 	org.olat.admin.user.delete.service.UserDeletionManagerTest.class,//ok
 	org.olat.group.test.BGRightManagerTest.class,//ok
 	org.olat.group.test.BGAreaManagerTest.class,//ok