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