From ac3d30fdd1947a34f144907311a11267617afb8c Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Mon, 17 Dec 2012 16:18:16 +0100
Subject: [PATCH] OO-466: control indexer and notifications periodically jobs
 only via quartz scheduler, cron expression customizable, RESt API to trigger
 the two jobs

---
 .../NotificationsEmailAdminController.java    |  16 +-
 .../admin/search/SearchAdminController.java   |  24 +-
 .../commons/services/search/SearchModule.java |  11 -
 .../services/search/SearchService.java        |  13 -
 .../restapi/security/RestApiLoginFilter.java  |  27 ++-
 .../restapi/system/IndexerWebService.java     | 110 +++++++++
 .../system/NotificationsAdminWebService.java  |  96 ++++++++
 .../system/OpenOLATStatisticsWebService.java  |  38 +--
 .../olat/restapi/system/SystemWebService.java |  16 ++
 .../olat/restapi/system/vo/IndexerStatus.java |  56 +++++
 .../system/vo/NotificationsStatus.java        |  54 +++++
 .../org/olat/search/_spring/searchContext.xml |   4 +-
 .../search/service/SearchServiceImpl.java     |  65 +++--
 .../service/indexer/OlatFullIndexer.java      |  96 +++-----
 .../service/indexer/SearchIndexingJob.java    |  19 +-
 .../search/service/update/IndexUpdater.java   | 229 ------------------
 .../olat/search/service/update/package.html   |   9 -
 .../resources/serviceconfig/olat.properties   |   4 +
 .../_spring/olatdefaultconfig.xml             |   2 +-
 19 files changed, 484 insertions(+), 405 deletions(-)
 create mode 100644 src/main/java/org/olat/restapi/system/IndexerWebService.java
 create mode 100644 src/main/java/org/olat/restapi/system/NotificationsAdminWebService.java
 create mode 100644 src/main/java/org/olat/restapi/system/vo/IndexerStatus.java
 create mode 100644 src/main/java/org/olat/restapi/system/vo/NotificationsStatus.java
 delete mode 100644 src/main/java/org/olat/search/service/update/IndexUpdater.java
 delete mode 100644 src/main/java/org/olat/search/service/update/package.html

diff --git a/src/main/java/org/olat/admin/notifications/NotificationsEmailAdminController.java b/src/main/java/org/olat/admin/notifications/NotificationsEmailAdminController.java
index 1362bf3fcf8..2e33fbdee0d 100644
--- a/src/main/java/org/olat/admin/notifications/NotificationsEmailAdminController.java
+++ b/src/main/java/org/olat/admin/notifications/NotificationsEmailAdminController.java
@@ -33,9 +33,12 @@ import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
-import org.olat.core.util.notifications.NotificationsManager;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
 import org.springframework.scheduling.quartz.CronTriggerBean;
 
+
 /**
  * Description:<br>
  * Manually trigger sending of notification email which are normally sent only once a day.
@@ -74,12 +77,17 @@ public class NotificationsEmailAdminController extends BasicController {
 	 *      org.olat.core.gui.control.Event)
 	 */
 	@Override
-	@SuppressWarnings("unused")
 	public void event(UserRequest ureq, Component source, Event event) {
 		if (source == startNotifyButton) {
-			NotificationsManager.getInstance().notifyAllSubscribersByEmail();
+			//trigger the cron job
+			try {
+				Scheduler scheduler = CoreSpringFactory.getImpl(Scheduler.class);
+				JobDetail detail = scheduler.getJobDetail("org.olat.notifications.job.enabled", Scheduler.DEFAULT_GROUP);
+				scheduler.triggerJob(detail.getName(), detail.getGroup());
+			} catch (SchedulerException e) {
+				logError("", e);
+			}
 		}
-
 	}
 
 	/**
diff --git a/src/main/java/org/olat/admin/search/SearchAdminController.java b/src/main/java/org/olat/admin/search/SearchAdminController.java
index 367170b5b2c..f8dc8cbaf03 100644
--- a/src/main/java/org/olat/admin/search/SearchAdminController.java
+++ b/src/main/java/org/olat/admin/search/SearchAdminController.java
@@ -100,17 +100,19 @@ public class SearchAdminController extends BasicController {
 		putInitialPanel(main);
 	}
 
+	protected void doDispose() {		
+		//
+	}
+
 	/**
 	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
 	 */
 	public void event(UserRequest ureq, Component source, Event event) {
 		if (source == startIndexingButton) {
-			SearchServiceFactory.getService().startIndexing();
-			logInfo("Indexing started via Admin", SearchAdminController.class.getName());
+			doStartIndexer();
 			myContent.setDirty(true);
 		} else if (source == stopIndexingButton) {
-			SearchServiceFactory.getService().stopIndexing();
-			logInfo("Indexing stopped via Admin", SearchAdminController.class.getName());
+			doStopIndexer();
 			myContent.setDirty(true);
 		}
 	}
@@ -133,11 +135,13 @@ public class SearchAdminController extends BasicController {
 		}
 	}
 	
-	/**
-	 * 
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
-	protected void doDispose() {		
-		//
+	private void doStartIndexer() {
+		SearchServiceFactory.getService().startIndexing();
+		logInfo("Indexing started via Admin", SearchAdminController.class.getName());
+	}
+	
+	private void doStopIndexer() {
+		SearchServiceFactory.getService().stopIndexing();
+		logInfo("Indexing started via Admin", SearchAdminController.class.getName());
 	}
 }
diff --git a/src/main/java/org/olat/core/commons/services/search/SearchModule.java b/src/main/java/org/olat/core/commons/services/search/SearchModule.java
index b1865bf0bad..a2453eaca62 100644
--- a/src/main/java/org/olat/core/commons/services/search/SearchModule.java
+++ b/src/main/java/org/olat/core/commons/services/search/SearchModule.java
@@ -54,7 +54,6 @@ public class SearchModule extends AbstractOLATModule {
 	public final static String CONF_TEMP_INDEX_PATH = "tempIndexPath";
 	public final static String CONF_TEMP_SPELL_CHECK_PATH = "tempSpellCheckPath";
 	public final static String CONF_GENERATE_AT_STARTUP = "generateIndexAtStartup";
-	private static final String CONF_RESTART_INTERVAL = "restartInterval";
 	private static final String CONF_INDEX_INTERVAL = "indexInterval";
 	private static final String CONF_MAX_HITS = "maxHits";
 	private static final String CONF_MAX_RESULTS = "maxResults";
@@ -77,7 +76,6 @@ public class SearchModule extends AbstractOLATModule {
 	private static final String CONF_FILE_BLACK_LIST = "fileBlackList";
 	
 	// Default values
-	private static final int    DEFAULT_RESTART_INTERVAL = 0;
 	private static final int    DEFAULT_INDEX_INTERVAL = 0;
 	private static final int    DEFAULT_MAX_HITS = 1000;
 	private static final int    DEFAULT_MAX_RESULTS = 100;
@@ -94,7 +92,6 @@ public class SearchModule extends AbstractOLATModule {
 	private String fullTempIndexPath;
 	private String fullTempSpellCheckPath;
 	private long indexInterval;
-	private long restartInterval;
 	private boolean generateAtStartup;
 	private int maxHits;
 	private int maxResults;
@@ -170,7 +167,6 @@ public class SearchModule extends AbstractOLATModule {
     fullPdfTextBufferPath = buildPath(tempPdfTextBufferPath);
 
     generateAtStartup = getBooleanConfigParameter(CONF_GENERATE_AT_STARTUP, true);
-    restartInterval = getIntConfigParameter(CONF_RESTART_INTERVAL, DEFAULT_RESTART_INTERVAL);
     indexInterval = getIntConfigParameter(CONF_INDEX_INTERVAL, DEFAULT_INDEX_INTERVAL);
     maxHits = getIntConfigParameter(CONF_MAX_HITS, DEFAULT_MAX_HITS);
     maxResults = getIntConfigParameter(CONF_MAX_RESULTS, DEFAULT_MAX_RESULTS);
@@ -280,13 +276,6 @@ public class SearchModule extends AbstractOLATModule {
 		return generateAtStartup;
 	}
 
-	/**
-	 * @return Time in millisecond between restart generation of a full-index.
-	 */
-	public long getRestartInterval() {
-		return restartInterval;
-	}
-
 	/**
 	 * @return Sleep time in millisecond between indexing documents.
 	 */
diff --git a/src/main/java/org/olat/core/commons/services/search/SearchService.java b/src/main/java/org/olat/core/commons/services/search/SearchService.java
index d22b89955c7..34feb9ed880 100644
--- a/src/main/java/org/olat/core/commons/services/search/SearchService.java
+++ b/src/main/java/org/olat/core/commons/services/search/SearchService.java
@@ -28,7 +28,6 @@ package org.olat.core.commons.services.search;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.lucene.document.Document;
 import org.apache.lucene.queryParser.ParseException;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
@@ -77,18 +76,6 @@ public interface SearchService {
 	 * @return
 	 */
 	public SearchServiceStatus getStatus();
-
-	/**
-	 * Add a document to existing index.
-	 * @param document  New document.
-	 */
-	public void addToIndex(Document document);
-
-	/**
-	 * Delete a document in existing index.
-	 * @param document  Delete this document. 
-	 */
-	public void deleteFromIndex(Document document);
 	
 	/**
 	 * Get index-interval of running system
diff --git a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
index a273947d1b3..1951dc1ba93 100644
--- a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
+++ b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java
@@ -71,6 +71,7 @@ public class RestApiLoginFilter implements Filter {
 	private static final String BASIC_AUTH_REALM = "OLAT Rest API";
 	
 	private static List<String> openUrls;
+	private static List<String> alwaysEnabledUrls;
 	private static String LOGIN_URL;
 	
 	/**
@@ -98,8 +99,9 @@ public class RestApiLoginFilter implements Filter {
 				HttpServletRequest httpRequest = (HttpServletRequest)request;
 				HttpServletResponse httpResponse = (HttpServletResponse)response;
 				
+				String requestURI = httpRequest.getRequestURI();
 				RestModule restModule = (RestModule)CoreSpringFactory.getBean("restModule");
-				if(!restModule.isEnabled()) {
+				if(!restModule.isEnabled() && !isRequestURIAlwaysEnabled(requestURI)) {
 					httpResponse.sendError(403);
 					return;
 				}
@@ -114,7 +116,6 @@ public class RestApiLoginFilter implements Filter {
 					//use the available session
 					followSession(httpRequest, httpResponse, chain);
 				} else {
-					String requestURI = httpRequest.getRequestURI();
 					if(requestURI.startsWith(getLoginUrl())) {
 						followForAuthentication(requestURI, uress, httpRequest, httpResponse, chain);
 					} else if(isRequestURIInOpenSpace(requestURI)) {
@@ -209,6 +210,15 @@ public class RestApiLoginFilter implements Filter {
 		return false;
 	}
 	
+	private boolean isRequestURIAlwaysEnabled(String requestURI) {
+		for(String openURI : getAlwaysEnabledURIs()) {
+			if(requestURI.startsWith(openURI)) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
 	private void followForAuthentication(String requestURI, UserSession uress, HttpServletRequest request, HttpServletResponse response, FilterChain chain) 
 	throws IOException, ServletException {
 	//create a session for login without security check
@@ -319,6 +329,19 @@ public class RestApiLoginFilter implements Filter {
 		return LOGIN_URL;
 	}
 	
+	private List<String> getAlwaysEnabledURIs() {
+		if(alwaysEnabledUrls == null) {
+			String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT);
+			alwaysEnabledUrls = new ArrayList<String>();
+			alwaysEnabledUrls.add(context + "/i18n");
+			alwaysEnabledUrls.add(context + "/api");
+			alwaysEnabledUrls.add(context + "/ping");
+			alwaysEnabledUrls.add(context + "/openmeetings");
+			alwaysEnabledUrls.add(context + "/system");
+		}
+		return alwaysEnabledUrls;
+	}
+	
 	private List<String> getOpenURIs() {
 		if(openUrls == null) {
 			String context = (Settings.isJUnitTest() ? "/olat" : WebappHelper.getServletContextPath() + RestSecurityHelper.SUB_CONTEXT);
diff --git a/src/main/java/org/olat/restapi/system/IndexerWebService.java b/src/main/java/org/olat/restapi/system/IndexerWebService.java
new file mode 100644
index 00000000000..01de03c8078
--- /dev/null
+++ b/src/main/java/org/olat/restapi/system/IndexerWebService.java
@@ -0,0 +1,110 @@
+/**
+ * <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.restapi.system;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.olat.core.commons.services.search.SearchServiceStatus;
+import org.olat.restapi.system.vo.IndexerStatisticsVO;
+import org.olat.restapi.system.vo.IndexerStatus;
+import org.olat.search.service.SearchServiceFactory;
+import org.olat.search.service.SearchServiceStatusImpl;
+import org.olat.search.service.indexer.FullIndexerStatus;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class IndexerWebService {
+	
+	/**
+	 * Return the statistics about the indexer
+	 * @response.representation.200.qname {http://www.example.com}releaseVO
+   * @response.representation.200.mediaType application/xml, application/json
+   * @response.representation.200.doc The verison of the instance
+   * @response.representation.200.example {@link org.olat.restapi.system.vo.Examples#SAMPLE_OO_INDEXERSTATSVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+
+	 * @return The statistics about the indexer
+	 */
+	@GET
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getStatistics() {
+		IndexerStatisticsVO stats = getIndexerStatistics();
+		return Response.ok(stats).build();
+	}
+	
+	@GET
+	@Path("status")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getStatus(@Context HttpServletRequest request) {
+		SearchServiceStatus serviceStatus = SearchServiceFactory.getService().getStatus();
+		String status = serviceStatus.getStatus();
+		return Response.ok(new IndexerStatus(status)).build();
+	}
+	
+	@GET
+	@Path("status")
+	@Produces({MediaType.TEXT_PLAIN})
+	public Response getPlainTextStatus(@Context HttpServletRequest request) {
+		SearchServiceStatus serviceStatus = SearchServiceFactory.getService().getStatus();
+		return Response.ok(serviceStatus.getStatus()).build();
+	}
+	
+	@POST
+	@Path("status")
+	public Response setStatus(@FormParam("status") String status, @Context HttpServletRequest request) {
+		if(FullIndexerStatus.STATUS_RUNNING.equals(status)) {
+			SearchServiceFactory.getService().startIndexing();
+		} else if(FullIndexerStatus.STATUS_STOPPED.equals(status)) {
+			SearchServiceFactory.getService().stopIndexing();
+		}
+		return Response.ok().build();
+	}
+	
+	protected IndexerStatisticsVO getIndexerStatistics() {
+		IndexerStatisticsVO stats = new IndexerStatisticsVO();
+
+		SearchServiceStatus status = SearchServiceFactory.getService().getStatus();
+		if(status instanceof SearchServiceStatusImpl) {
+			SearchServiceStatusImpl statusImpl = (SearchServiceStatusImpl)status;
+			FullIndexerStatus fStatus = statusImpl.getFullIndexerStatus();
+			stats.setIndexedDocumentCount(fStatus.getDocumentCount());
+			stats.setExcludedDocumentCount(fStatus.getExcludedDocumentCount());
+			stats.setIndexSize(fStatus.getIndexSize());
+			stats.setIndexingTime(fStatus.getIndexingTime());
+			stats.setFullIndexStartedAt(fStatus.getFullIndexStartedAt());
+			stats.setDocumentQueueSize(fStatus.getDocumentQueueSize());
+			stats.setRunningFolderIndexerCount(fStatus.getNumberRunningFolderIndexer());
+			stats.setAvailableFolderIndexerCount(fStatus.getNumberAvailableFolderIndexer());
+			stats.setLastFullIndexTime(fStatus.getLastFullIndexTime());
+		}
+		stats.setStatus(status.getStatus());
+		return stats;
+	}
+}
diff --git a/src/main/java/org/olat/restapi/system/NotificationsAdminWebService.java b/src/main/java/org/olat/restapi/system/NotificationsAdminWebService.java
new file mode 100644
index 00000000000..7db133f80cb
--- /dev/null
+++ b/src/main/java/org/olat/restapi/system/NotificationsAdminWebService.java
@@ -0,0 +1,96 @@
+/**
+ * <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.restapi.system;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.restapi.system.vo.NotificationsStatus;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class NotificationsAdminWebService {
+	
+	private static final OLog log = Tracing.createLoggerFor(NotificationsAdminWebService.class);
+	
+	@GET
+	@Path("status")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getStatus(@Context HttpServletRequest request) {
+		return Response.ok(new NotificationsStatus(getJobStatus())).build();
+	}
+	
+	@GET
+	@Path("status")
+	@Produces({MediaType.TEXT_PLAIN})
+	public Response getPlainTextStatus(@Context HttpServletRequest request) {
+		return Response.ok(getJobStatus()).build();
+	}
+	
+	private String getJobStatus() {
+		try {
+			Scheduler scheduler = CoreSpringFactory.getImpl(Scheduler.class);
+			@SuppressWarnings("unchecked")
+			List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
+			for(JobExecutionContext job:jobs) {
+				if("org.olat.notifications.job.enabled".equals(job.getJobDetail().getName())) {
+					return "running";
+				}
+			}
+			return "stopped";
+		} catch (SchedulerException e) {
+			log.error("", e);
+			return "error";
+		}
+	}
+	
+	@POST
+	@Path("status")
+	public Response setStatus(@FormParam("status") String status, @Context HttpServletRequest request) {
+		if("running".equals(status)) {
+			try {
+				Scheduler scheduler = CoreSpringFactory.getImpl(Scheduler.class);
+				JobDetail detail = scheduler.getJobDetail("org.olat.notifications.job.enabled", Scheduler.DEFAULT_GROUP);
+				scheduler.triggerJob(detail.getName(), detail.getGroup());
+			} catch (SchedulerException e) {
+				log.error("", e);
+			}
+		}
+		return Response.ok().build();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/restapi/system/OpenOLATStatisticsWebService.java b/src/main/java/org/olat/restapi/system/OpenOLATStatisticsWebService.java
index 553deac27c4..dfa9d9f89f9 100644
--- a/src/main/java/org/olat/restapi/system/OpenOLATStatisticsWebService.java
+++ b/src/main/java/org/olat/restapi/system/OpenOLATStatisticsWebService.java
@@ -31,7 +31,6 @@ import javax.ws.rs.core.Response;
 
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.services.search.SearchServiceStatus;
 import org.olat.core.util.SessionInfo;
 import org.olat.core.util.UserSession;
 import org.olat.core.util.session.UserSessionManager;
@@ -40,14 +39,10 @@ import org.olat.group.BusinessGroupService;
 import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
-import org.olat.restapi.system.vo.IndexerStatisticsVO;
 import org.olat.restapi.system.vo.OpenOLATStatisticsVO;
 import org.olat.restapi.system.vo.RepositoryStatisticsVO;
 import org.olat.restapi.system.vo.SessionsVO;
 import org.olat.restapi.system.vo.UserStatisticsVO;
-import org.olat.search.service.SearchServiceFactory;
-import org.olat.search.service.SearchServiceStatusImpl;
-import org.olat.search.service.indexer.FullIndexerStatus;
 
 /**
  * 
@@ -55,6 +50,8 @@ import org.olat.search.service.indexer.FullIndexerStatus;
  */
 public class OpenOLATStatisticsWebService {
 	
+	private final IndexerWebService indexerWebService = new IndexerWebService();
+	
 	/**
 	 * Return the statistics about OpenOLAT, users count, courses count... 
 	 * @response.representation.200.qname {http://www.example.com}releaseVO
@@ -72,7 +69,7 @@ public class OpenOLATStatisticsWebService {
 		stats.setSessions(getSessionsVO());
 		stats.setUserStatistics(getUserStatistics());
 		stats.setRepositoryStatistics(getRepositoryStatistics());
-		stats.setIndexerStatistics(getIndexerStatistics());
+		stats.setIndexerStatistics(indexerWebService.getIndexerStatistics());
 		return Response.ok(stats).build();
 	}
 	
@@ -122,12 +119,9 @@ public class OpenOLATStatisticsWebService {
    * @param request The HTTP request
 	 * @return The statistics about the indexer
 	 */
-	@GET
 	@Path("indexer")
-	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
-	public Response getIndexerStatistics(@Context HttpServletRequest request) {
-		IndexerStatisticsVO stats = getIndexerStatistics();
-		return Response.ok(stats).build();
+	public IndexerWebService getIndexerStatistics(@Context HttpServletRequest request) {
+		return indexerWebService;
 	}
 
 	/**
@@ -171,28 +165,6 @@ public class OpenOLATStatisticsWebService {
 		return stats;
 	}
 	
-	private IndexerStatisticsVO getIndexerStatistics() {
-		IndexerStatisticsVO stats = new IndexerStatisticsVO();
-
-		SearchServiceStatus status = SearchServiceFactory.getService().getStatus();
-		if(status instanceof SearchServiceStatusImpl) {
-			SearchServiceStatusImpl statusImpl = (SearchServiceStatusImpl)status;
-			FullIndexerStatus fStatus = statusImpl.getFullIndexerStatus();
-			stats.setIndexedDocumentCount(fStatus.getDocumentCount());
-			stats.setExcludedDocumentCount(fStatus.getExcludedDocumentCount());
-			stats.setIndexSize(fStatus.getIndexSize());
-			stats.setIndexingTime(fStatus.getIndexingTime());
-			stats.setFullIndexStartedAt(fStatus.getFullIndexStartedAt());
-			stats.setDocumentQueueSize(fStatus.getDocumentQueueSize());
-			stats.setRunningFolderIndexerCount(fStatus.getNumberRunningFolderIndexer());
-			stats.setAvailableFolderIndexerCount(fStatus.getNumberAvailableFolderIndexer());
-			stats.setLastFullIndexTime(fStatus.getLastFullIndexTime());
-		}
-		stats.setStatus(status.getStatus());
-
-		return stats;
-	}
-	
 	private SessionsVO getSessionsVO() {
 		SessionsVO vo = new SessionsVO();
 
diff --git a/src/main/java/org/olat/restapi/system/SystemWebService.java b/src/main/java/org/olat/restapi/system/SystemWebService.java
index fb3749d45f6..f3b27963c14 100644
--- a/src/main/java/org/olat/restapi/system/SystemWebService.java
+++ b/src/main/java/org/olat/restapi/system/SystemWebService.java
@@ -118,6 +118,22 @@ public class SystemWebService {
 		return new MonitoringWebService();
 	}
 	
+	@Path("indexer")
+	public IndexerWebService getIndexer(@Context HttpServletRequest request) {
+		if(!isAdmin(request)) {
+			return null;
+		}
+		return new IndexerWebService();
+	}
+	
+	@Path("notifications")
+	public NotificationsAdminWebService getNotifications(@Context HttpServletRequest request) {
+		if(!isAdmin(request)) {
+			return null;
+		}
+		return new NotificationsAdminWebService();
+	}
+	
 	private boolean isMonitoringEnabled() {
 		MonitoringModule module = CoreSpringFactory.getImpl(MonitoringModule.class);
 		return module.isEnabled();
diff --git a/src/main/java/org/olat/restapi/system/vo/IndexerStatus.java b/src/main/java/org/olat/restapi/system/vo/IndexerStatus.java
new file mode 100644
index 00000000000..0f131a8948c
--- /dev/null
+++ b/src/main/java/org/olat/restapi/system/vo/IndexerStatus.java
@@ -0,0 +1,56 @@
+/**
+ * <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.restapi.system.vo;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.olat.core.commons.services.search.SearchServiceStatus;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "indexerStatusVO")
+public class IndexerStatus implements SearchServiceStatus {
+
+	@XmlAttribute(name="status")
+	private String status;
+	
+	public IndexerStatus() {
+		//
+	}
+	
+	public IndexerStatus(String status) {
+		this.status = status;
+	}
+
+	public String getStatus() {
+		return status;
+	}
+
+	public void setStatus(String status) {
+		this.status = status;
+	}
+}
diff --git a/src/main/java/org/olat/restapi/system/vo/NotificationsStatus.java b/src/main/java/org/olat/restapi/system/vo/NotificationsStatus.java
new file mode 100644
index 00000000000..25531aa7c8c
--- /dev/null
+++ b/src/main/java/org/olat/restapi/system/vo/NotificationsStatus.java
@@ -0,0 +1,54 @@
+/**
+ * <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.restapi.system.vo;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "notificationsStatusVO")
+public class NotificationsStatus {
+
+	@XmlAttribute(name="status")
+	private String status;
+	
+	public NotificationsStatus() {
+		//
+	}
+	
+	public NotificationsStatus(String status) {
+		this.status = status;
+	}
+
+	public String getStatus() {
+		return status;
+	}
+
+	public void setStatus(String status) {
+		this.status = status;
+	}
+}
diff --git a/src/main/java/org/olat/search/_spring/searchContext.xml b/src/main/java/org/olat/search/_spring/searchContext.xml
index 21f9378f6aa..f8684d98d21 100644
--- a/src/main/java/org/olat/search/_spring/searchContext.xml
+++ b/src/main/java/org/olat/search/_spring/searchContext.xml
@@ -15,6 +15,7 @@
 		<constructor-arg index="0" ref="searchModule" />
 		<constructor-arg index="1" ref="mainIndexer" />
 		<constructor-arg index="2" ref="searchProvider" />
+		<constructor-arg index="3" ref="schedulerFactoryBean"/>
 	    <!-- When index is older than maxIndexTime in millis, an error will be logged 432000000 = 5d, 86400 = 1d -->
 	   	<property name="maxIndexTime" value="432000000"/>
 		<property name="metadataFields" ref="SearchMetadataFieldsProvider" />
@@ -69,9 +70,6 @@
 	       <property name="arguments">
 	             <value>
 	        		generateIndexAtStartup=${generate.index.at.startup}		
-					<!-- restartInterval in ms (0=no restart) 
-					fxdiff FXOLAT-221: start indexer at different times for each instance -->
-					restartInterval=${search.indexing.restart.interval}	
 					tempIndexPath=${search.index.tempIndex}
 					tempSpellCheckPath=${search.index.tempSpellcheck}
 					pdfTextBufferPath=${search.index.pdfBuffer}
diff --git a/src/main/java/org/olat/search/service/SearchServiceImpl.java b/src/main/java/org/olat/search/service/SearchServiceImpl.java
index 5067b9fc353..b2e788c2769 100644
--- a/src/main/java/org/olat/search/service/SearchServiceImpl.java
+++ b/src/main/java/org/olat/search/service/SearchServiceImpl.java
@@ -34,7 +34,6 @@ import java.util.Set;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.queryParser.MultiFieldQueryParser;
 import org.apache.lucene.queryParser.ParseException;
@@ -48,6 +47,7 @@ import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
 import org.apache.lucene.util.Version;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.search.AbstractOlatDocument;
 import org.olat.core.commons.services.search.SearchModule;
 import org.olat.core.commons.services.search.SearchResults;
@@ -67,7 +67,9 @@ import org.olat.search.service.indexer.MainIndexer;
 import org.olat.search.service.searcher.JmsSearchProvider;
 import org.olat.search.service.searcher.SearchResultsImpl;
 import org.olat.search.service.spell.SearchSpellChecker;
-import org.olat.search.service.update.IndexUpdater;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
 
 /**
  * 
@@ -78,8 +80,8 @@ public class SearchServiceImpl implements SearchService {
 	
 	private Index indexer;	
 	private SearchModule searchModuleConfig;
-	private IndexUpdater indexUpdater;
 	private MainIndexer mainIndexer;
+	private Scheduler scheduler;
 
 	private long maxIndexTime;
 	private Analyzer analyzer;
@@ -101,37 +103,49 @@ public class SearchServiceImpl implements SearchService {
 	/**
 	 * [used by spring]
 	 */
-	private SearchServiceImpl(SearchModule searchModule, MainIndexer mainIndexer, JmsSearchProvider searchProvider) {
+	private SearchServiceImpl(SearchModule searchModule, MainIndexer mainIndexer, JmsSearchProvider searchProvider, Scheduler scheduler) {
 		log.info("Start SearchServiceImpl constructor...");
+		this.scheduler = scheduler;
 		this.searchModuleConfig = searchModule;
 		this.mainIndexer = mainIndexer;
-		analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
+		analyzer = new StandardAnalyzer(Version.LUCENE_30);
 		searchProvider.setSearchService(this);
-
-	}
-		
-	public void addToIndex(Document document) {
-		if (indexUpdater==null) throw new AssertException ("Try to call addToIndex() but indexUpdater is null!");
-		log.info("addToIndex document=" + document);
-		indexUpdater.addToIndex(document);
 	}
 	
+	/**
+	 * Start the job indexer
+	 */
 	public void startIndexing() {
 		if (indexer==null) throw new AssertException ("Try to call startIndexing() but indexer is null");
-		indexer.startFullIndex();
-		log.info("startIndexing...");
+		
+		try {
+			Scheduler scheduler = CoreSpringFactory.getImpl(Scheduler.class);
+			JobDetail detail = scheduler.getJobDetail("org.olat.search.job.enabled", Scheduler.DEFAULT_GROUP);
+			scheduler.triggerJob(detail.getName(), detail.getGroup());
+			log.info("startIndexing...");
+		} catch (SchedulerException e) {
+			log.error("Error trigerring the indexer job: ", e);
+		}
 	}
 
+	/**
+	 * Interrupt the job indexer
+	 */
 	public void stopIndexing() {
 		if (indexer==null) throw new AssertException ("Try to call stopIndexing() but indexer is null");
-		indexer.stopFullIndex();
-		log.info("stopIndexing.");
-	}
 
-	public void deleteFromIndex(Document document) {
-		if (indexUpdater==null) throw new AssertException ("Try to call deleteFromIndex() but indexUpdater is null");
-		log.info("deleteFromIndex document=" + document);
-		indexUpdater.deleteFromIndex(document);
+		try {
+			Scheduler scheduler = CoreSpringFactory.getImpl(Scheduler.class);
+			JobDetail detail = scheduler.getJobDetail("org.olat.search.job.enabled", Scheduler.DEFAULT_GROUP);
+			scheduler.interrupt(detail.getName(), detail.getGroup());
+			log.info("stopIndexing.");
+		} catch (SchedulerException e) {
+			log.error("Error interrupting the indexer job: ", e);
+		}
+	}
+	
+	public Index getInternalIndexer() {
+		return indexer;
 	}
 
 	public void init() {
@@ -140,7 +154,6 @@ public class SearchServiceImpl implements SearchService {
 		log.info("Running with indexPath=" + searchModuleConfig.getFullIndexPath());
 		log.info("        tempIndexPath=" + searchModuleConfig.getFullTempIndexPath());
 		log.info("        generateAtStartup=" + searchModuleConfig.getGenerateAtStartup());
-		log.info("        restartInterval=" + searchModuleConfig.getRestartInterval());
 		log.info("        indexInterval=" + searchModuleConfig.getIndexInterval());
 
 		searchSpellChecker = new SearchSpellChecker();
@@ -149,7 +162,6 @@ public class SearchServiceImpl implements SearchService {
 		searchSpellChecker.setSpellCheckEnabled(searchModuleConfig.getSpellCheckEnabled());
 		
 	  indexer = new Index(searchModuleConfig, searchSpellChecker, mainIndexer);
-	  indexUpdater = new IndexUpdater(searchModuleConfig.getFullIndexPath(), searchModuleConfig.getUpdateInterval());
 
 	  indexPath = searchModuleConfig.getFullIndexPath();
 
@@ -160,7 +172,12 @@ public class SearchServiceImpl implements SearchService {
 		}		
 
   	if (startingFullIndexingAllowed()) {
-  		indexer.startFullIndex();
+  		try {
+				JobDetail detail = scheduler.getJobDetail("org.olat.search.job.enabled", Scheduler.DEFAULT_GROUP);
+				scheduler.triggerJob(detail.getName(), detail.getGroup());
+			} catch (SchedulerException e) {
+				log.error("", e);
+			}
   	}
   	log.info("init DONE");
 	}
diff --git a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
index dadd70dfef0..ef56dd4ed60 100644
--- a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
@@ -32,13 +32,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
+import org.apache.lucene.LucenePackage;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.LucenePackage;
 import org.apache.lucene.util.Version;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.commons.services.search.OlatDocument;
@@ -51,14 +51,14 @@ import org.olat.search.service.SearchResourceContext;
  * Controls the hole generation of a full-index. Runs in own thread.
  * @author Christian Guretzki
  */
-public class OlatFullIndexer implements Runnable {
+public class OlatFullIndexer {
 	
 	private OLog log = Tracing.createLoggerFor(OlatFullIndexer.class);
 	
 	/* TODO:chg: define merge-factor in olat-config.*/
 	private static final int INDEX_MERGE_FACTOR = 1000;
 
-	private static final int MAX_SIZE_QUEUE = 10000;
+	private static final int MAX_SIZE_QUEUE = 500;
 	private int numberIndexWriter = 5;
 
 	private String  tempIndexPath;
@@ -67,17 +67,10 @@ public class OlatFullIndexer implements Runnable {
 	 * Reference to indexer for done callback.
 	 */
 	private Index index;
-	
-	private Thread indexingThread = null;
-	
 	private IndexWriter indexWriter = null;
 	
 	/** Flag to stop indexing. */
 	private boolean stopIndexing;
-
-
-  /** When restartIndexingWhenFinished is true, the restart interval in ms can be set. */
-	private long restartInterval;
   /** When restartIndexingWhenFinished is true, the restart interval in ms can be set. */
 	private long indexInterval = 500;
 	
@@ -116,7 +109,6 @@ public class OlatFullIndexer implements Runnable {
     this.index = index;
     this.mainIndexer = mainIndexer;
     tempIndexPath        = searchModuleConfig.getFullTempIndexPath();
-    restartInterval      = searchModuleConfig.getRestartInterval();
     indexInterval        = searchModuleConfig.getIndexInterval();
     numberIndexWriter    = searchModuleConfig.getNumberIndexWriter();
     documentsPerInterval = searchModuleConfig.getDocumentsPerInterval();
@@ -133,19 +125,11 @@ public class OlatFullIndexer implements Runnable {
 	 */
 	public void startIndexing() {
     //	 Start updateThread
-		if ( (indexingThread == null) || !indexingThread.isAlive()) {
-			if (stopIndexing) {
-				log.info("start full indexing thread...");
-				indexingThread = new Thread(this, "FullIndexer");
-				stopIndexing = false;
-				resetDocumentCounters();
-		    // Set to lowest priority
-		    indexingThread.setPriority(Thread.MIN_PRIORITY);
-		    indexingThread.setDaemon(true);
-				indexingThread.start();
-			}
-		} else {
-			log.debug("indexing allready running");
+		if (stopIndexing) {
+			log.info("start full indexing thread...");
+			stopIndexing = false;
+			resetDocumentCounters();
+			run();
 		}
 	}
 
@@ -153,11 +137,8 @@ public class OlatFullIndexer implements Runnable {
 	 * Stop full indexer thread asynchron.
 	 */
 	public void stopIndexing() {
-		if ( (indexingThread != null) && indexingThread.isAlive()) {
-			stopIndexing = true;
-			indexingThread.interrupt();
-			if (log.isDebug()) log.debug("stop current indexing when");
-		}
+		stopIndexing = true;
+		if (log.isDebug()) log.debug("stop current indexing when");
 	}
 
 	/**
@@ -262,41 +243,30 @@ public class OlatFullIndexer implements Runnable {
 	 * @see java.lang.Runnable#run()
 	 */
 	public void run() {
-		boolean runAgain = true;
 		try {
 		  //TODO: Workround : does not start immediately
 			Thread.sleep(10000);
-			while (runAgain && !this.stopIndexing) {
-				log.info("full indexing starts... Lucene-version:" + LucenePackage.get().getImplementationVersion());
-				fullIndexerStatus.indexingStarted();
-				doIndex();
-				index.indexingIsDone();
-				fullIndexerStatus.indexingFinished();
-				log.info("full indexing done in " + fullIndexerStatus.getIndexingTime() + "ms");
-				
-				//OLAT-5630 - dump more infos about the indexer run - for analysis later
-				FullIndexerStatus status = getStatus();
-				log.info("full indexing summary: started:           "+status.getFullIndexStartedAt());
-				log.info("full indexing summary: counter:           "+status.getDocumentCount());
-				log.info("full indexing summary: index.per.minute:  "+status.getIndexPerMinute());
-				log.info("full indexing summary: finished:          "+status.getLastFullIndexTime());
-				log.info("full indexing summary: time:              "+status.getIndexingTime()+" ms");
-				log.info("full indexing summary: size:              "+status.getIndexSize());
-				
-				log.info("full indexing summary: document counters: "+status.getDocumentCounters());
-				log.info("full indexing summary: file type counters:"+status.getFileTypeCounters());
-				log.info("full indexing summary: excluded counter:  "+status.getExcludedDocumentCount());
 
-				if (restartInterval == 0) {
-					log.debug("do not run again");
-					runAgain = false;
-				} else {
-					if (log.isDebug()) log.debug("Indexing sleep=" + restartInterval + "ms");
-					fullIndexerStatus.setStatus(FullIndexerStatus.STATUS_SLEEPING);
-					Thread.sleep(restartInterval);
-					if (log.isDebug()) log.debug("Restart indexing");
-				}
-			}
+			log.info("full indexing starts... Lucene-version:" + LucenePackage.get().getImplementationVersion());
+			fullIndexerStatus.indexingStarted();
+			doIndex();
+			index.indexingIsDone();
+			fullIndexerStatus.indexingFinished();
+			log.info("full indexing done in " + fullIndexerStatus.getIndexingTime() + "ms");
+			
+			//OLAT-5630 - dump more infos about the indexer run - for analysis later
+			FullIndexerStatus status = getStatus();
+			log.info("full indexing summary: started:           "+status.getFullIndexStartedAt());
+			log.info("full indexing summary: counter:           "+status.getDocumentCount());
+			log.info("full indexing summary: index.per.minute:  "+status.getIndexPerMinute());
+			log.info("full indexing summary: finished:          "+status.getLastFullIndexTime());
+			log.info("full indexing summary: time:              "+status.getIndexingTime()+" ms");
+			log.info("full indexing summary: size:              "+status.getIndexSize());
+			
+			log.info("full indexing summary: document counters: "+status.getDocumentCounters());
+			log.info("full indexing summary: file type counters:"+status.getFileTypeCounters());
+			log.info("full indexing summary: excluded counter:  "+status.getExcludedDocumentCount());
+
 		} catch(InterruptedException iex) {
 			log.info("FullIndexer was interrupted ;" + iex.getMessage());
 		} catch(Throwable ex) {
@@ -308,13 +278,11 @@ public class OlatFullIndexer implements Runnable {
 		}
 		fullIndexerStatus.setStatus(FullIndexerStatus.STATUS_STOPPED);
 		stopIndexing = true;
-		indexingThread = null;
 		try {
 			log.info("quit indexing run.");
 		} catch (NullPointerException nex) {
 			// no logging available (shut down)=> do nothing
 		}
-		
 	}
 	
 	/**
@@ -331,7 +299,7 @@ public class OlatFullIndexer implements Runnable {
 				Thread.sleep(indexInterval);
 			} else {
 				// do not sleep, check for stopping indexing
-				if (this.stopIndexing) {
+				if (stopIndexing) {
 					throw new InterruptedException("Do stop indexing at element=" + indexWriter.maxDoc());
 				}
 			}
@@ -349,7 +317,7 @@ public class OlatFullIndexer implements Runnable {
 				countIndexPerMinute();
 				if (log.isDebug()) log.debug("documentQueue.add size=" + documentQueue.size());
 	      // check for stopping indexing
-				if (this.stopIndexing) {
+				if (stopIndexing) {
 					throw new InterruptedException("Do stop indexing at element=" + indexWriter.maxDoc());
 				}
 			}
diff --git a/src/main/java/org/olat/search/service/indexer/SearchIndexingJob.java b/src/main/java/org/olat/search/service/indexer/SearchIndexingJob.java
index cc3c51da890..4b2488fc757 100644
--- a/src/main/java/org/olat/search/service/indexer/SearchIndexingJob.java
+++ b/src/main/java/org/olat/search/service/indexer/SearchIndexingJob.java
@@ -25,11 +25,15 @@
 package org.olat.search.service.indexer;
 
 import org.olat.core.commons.scheduler.JobWithDB;
+import org.olat.core.commons.services.search.SearchService;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.search.service.SearchServiceFactory;
+import org.olat.search.service.SearchServiceImpl;
+import org.quartz.InterruptableJob;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
+import org.quartz.UnableToInterruptJobException;
 
 /**
  * Description:<br>
@@ -39,7 +43,7 @@ import org.quartz.JobExecutionException;
  * Initial Date:  09.09.2008 <br>
  * @author Christian Guretzki
  */
-public class SearchIndexingJob extends JobWithDB {
+public class SearchIndexingJob extends JobWithDB implements InterruptableJob {
 	private OLog log = Tracing.createLoggerFor(SearchIndexingJob.class);
 	
 	/**
@@ -49,7 +53,18 @@ public class SearchIndexingJob extends JobWithDB {
 	@Override
 	public void executeWithDB(JobExecutionContext arg0) throws JobExecutionException {
 		log.info("Search indexer started via cronjob.");
-		SearchServiceFactory.getService().startIndexing();
+		SearchService searchService = SearchServiceFactory.getService();
+		if(searchService instanceof SearchServiceImpl) {	
+			((SearchServiceImpl)searchService).getInternalIndexer().startFullIndex();
+		}
 	}
 
+	@Override
+	public void interrupt() throws UnableToInterruptJobException {
+		log.info("Interrupt indexer via quartz.");
+		SearchService searchService = SearchServiceFactory.getService();
+		if(searchService instanceof SearchServiceImpl) {	
+			((SearchServiceImpl)searchService).getInternalIndexer().stopFullIndex();
+		}
+	}
 }
diff --git a/src/main/java/org/olat/search/service/update/IndexUpdater.java b/src/main/java/org/olat/search/service/update/IndexUpdater.java
deleted file mode 100644
index cfa6f357fb1..00000000000
--- a/src/main/java/org/olat/search/service/update/IndexUpdater.java
+++ /dev/null
@@ -1,229 +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.search.service.update;
-
-import java.io.File;
-import java.util.List;
-import java.util.Vector;
-
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.util.Version;
-import org.olat.core.commons.services.search.OlatDocument;
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
-
-/**
- * The IndexUpdater thread controls the update of existing search index.
- * The update thread could be disabled with config parameter 'updateInterval=0'.
- * @author Christian Guretzki
- */
-public class IndexUpdater implements Runnable {
-	private static final int INDEX_MERGE_FACTOR = 1000;
-	private static OLog log = Tracing.createLoggerFor(IndexUpdater.class);
-
-	private String indexPath;
-
-	private Thread updaterThread = null;
-	private long updateInterval;
-
-	private boolean stopUpdater;
-
-	private List<Document> updateQueue;
-	private List<Document> deleteQueue;
-
-	/**
-	 * 
-	 * @param indexPath      Absolute file-path of existing index-directory which will be updated 
-	 * @param updateInterval Updater sleeps this time [ms] between running again. 
-	 */
-	public IndexUpdater(String indexPath, long updateInterval) {
-		this.indexPath = indexPath;
-		updateQueue = new Vector<Document>();
-		deleteQueue = new Vector<Document>();
-		this.updateInterval = updateInterval;
-		stopUpdater = true;
-		if (updateInterval != 0) {
-		  startUpdater();
-		} else {
-			log.info("IndexUpdater is disabled");
-		}
-	}
-	
-	/**
-	 * Add new or changed index document to update-queue.
-	 * @param document  New or changed index document.
-	 */
-	// o_clusterNOK: IndexUpdater is only prove of concept (with groups) and NOT designed for cluster !!!
-	public void addToIndex(Document document) {
-		// The IndexUpdate is disabled with updateInterval == 0 => do not add documents 
-		if (updateInterval != 0) {
-			updateQueue.add(document);
-		}
-	}
-
-	/**
-	 * Add index document to delete-queue.
-	 * @param document  Delete this index document.
-	 */
-	// o_clusterNOK: IndexUpdater is only prove of concept (with groups) and NOT designed for cluster !!!
-	public void deleteFromIndex(Document document) {
-		// The IndexUpdate is disabled with updateInterval == 0 => do not add documents 
-		if (updateInterval != 0) {
-		  deleteQueue.add(document);
-		}
-	}
-
-	public void run() {
-		boolean runAgain = true;
-		try {
-			while (runAgain && !this.stopUpdater) {
-				log.info("Updater starts...");
-				doUpdate();
-				log.info("Updater done ");
-				if (updateInterval == 0) {
-					log.debug("do not run again");
-					runAgain = false;
-				} else {
-					if (log.isDebug()) log.debug("Updater sleep=" + updateInterval + "ms");
-					Thread.sleep(updateInterval);
-					if (log.isDebug()) log.debug("Restart updater");
-				}
-			}
-		} catch(InterruptedException iex) {
-			log.info("FullIndexer was interrupted ;" + iex.getMessage());
-		}
-		stopUpdater = true;
-		log.info("quit indexing run.");
-	}
-
-	/**
-	 * Check update and delete-queue. Update existing index and writes new index file. 
-	 *
-	 */
-	private void doUpdate() {
-		if (!updateQueue.isEmpty() || !deleteQueue.isEmpty()) {
-			try {
-				log.info("updateQueue.size=" + updateQueue.size() + " deleteQueue.size" + deleteQueue.size());
-				// 0. make copy of queue's and delete it 
-				List<Document> updateCopy;
-				synchronized (updateQueue) {
-	 			  updateCopy = new Vector<Document>(updateQueue);
-	 			  updateQueue.clear();
-				}
-				List<Document> deleteCopy;
-				synchronized (deleteQueue) { 
-					deleteCopy = new Vector<Document>(deleteQueue);
-					deleteQueue.clear();
-				}
-				// 1. Open Index Reader
-				File indexFile = new File(indexPath);
-	      Directory directory = FSDirectory.open(indexFile);
-	      IndexReader indexReader = IndexReader.open(directory);
-
-	      log.info("befor delete: indexReader.numDocs()=" + indexReader.numDocs());
-				// 2. Delete old Document
-	      // loop over all documents in updateQueue
-	      for (int i = 0; i < updateCopy.size(); i++) {
-	      	String resourceUrl = updateCopy.get(i).get(OlatDocument.RESOURCEURL_FIELD_NAME);
-		      Term term = new Term(OlatDocument.RESOURCEURL_FIELD_NAME, resourceUrl );
-		      log.info("updateQueue:delete documents with resourceUrl=" + resourceUrl );
-		      indexReader.deleteDocuments(term);					
-				}
-	      // loop over all documents in deleteQueue
-	      for (int i = 0; i < deleteCopy.size(); i++) {
-	      	String resourceUrl = deleteCopy.get(i).get(OlatDocument.RESOURCEURL_FIELD_NAME);
-		      Term term = new Term(OlatDocument.RESOURCEURL_FIELD_NAME, resourceUrl );
-		      log.info("deleteQueue:delete documents with resourceUrl='" + resourceUrl + "'");
-		      indexReader.deleteDocuments(term);
-		      
-				}
-	      log.info("after delete: indexReader.numDocs()=" + indexReader.numDocs());
-				// 3. Close reader
-	      indexReader.close();
-	      directory.close();
-	      
-				// 4. open writer
-	      IndexWriter indexWriter = new IndexWriter(directory, new StandardAnalyzer(Version.LUCENE_CURRENT), false, IndexWriter.MaxFieldLength.UNLIMITED);
-				indexWriter.setMergeFactor(INDEX_MERGE_FACTOR); //for better performance
-				// 5. Add new Document
-	      for (int i = 0; i < updateCopy.size(); i++) {
-	      	Document document = updateCopy.get(i);
-	      	log.info("addDocument:" + document);
-	      	indexWriter.addDocument(document);					
-				}
-				// 6. Close writer
-	      long startOptimizeTime = 0;
-	      if (log.isDebug()) startOptimizeTime = System.currentTimeMillis();
-	      indexWriter.optimize();// TODO:chg: dauert ev. zulange oder nocht noetig
-	      if (log.isDebug()) log.debug("Optimized in " + (System.currentTimeMillis() - startOptimizeTime) + "ms");
-	      indexWriter.close();
-			} catch (Exception ex) {
-				log.warn("Exception during doUpdate. ", ex);
-			}
-		} else {
-			log.debug("Queues are ampty.");
-		}
-	}
-
-	/**
-	 * Start updater thread.
-	 */
-	public void startUpdater() {
-    //	 Start updateThread
-		if ( (updaterThread == null) || !updaterThread.isAlive()) {
-			log.info("start Updater thread...");
-			if (stopUpdater) {
-				updaterThread = new Thread(this, "Updater");
-				stopUpdater = false;
-		    // Set to lowest priority
-		    updaterThread.setPriority(Thread.MIN_PRIORITY);
-		    updaterThread.setDaemon(true);
-				updaterThread.start();
-			}
-		} else {
-			if (log.isDebug()) log.debug("Updater allready running");
-		}
-	}
-
-	/**
-	 * Stop update thread asynchron.
-	 */
-	public void stopUpdater() {
-		if (updaterThread.isAlive()) {
-			stopUpdater = true;
-			updaterThread.interrupt();
-			if (log.isDebug()) log.debug("stop Updater");
-		}
-	}
-
-	
-}
diff --git a/src/main/java/org/olat/search/service/update/package.html b/src/main/java/org/olat/search/service/update/package.html
deleted file mode 100644
index f76e0bbda8d..00000000000
--- a/src/main/java/org/olat/search/service/update/package.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<html>
-<head>
-</head>
-
-<body>
-This package includes all classes to update an existing index.
-Update includes adding new elements, delete existing elements or modify existing index-elements.
-</body>
-</html>
\ No newline at end of file
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index d2bd215ec6f..0bfebbc9011 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -193,6 +193,8 @@ notification.interval.daily=true
 notification.interval.half-daily=true
 notification.interval.four-hourly=true
 notification.interval.two-hourly=true
+#notification cron job
+notification.cronjob.expression=0 10 */2 * * ?
 
 ####################################################
 # Groups
@@ -557,6 +559,8 @@ search.indexing.cronjob=enabled
 # expression but have set search.indexing.cronjob=enabled, the system will generate a 
 # cron expression that triggers the indexer every four hour depending on your tomcat.id variable
 search.indexing.cronjob.expression=0 0 3 * * ?
+#examples:
+# never fire: 0 0 0 1 1 ? 3000
 
 ########################################################################
 # REST API
diff --git a/src/main/resources/serviceconfig/org/olat/notifications/_spring/olatdefaultconfig.xml b/src/main/resources/serviceconfig/org/olat/notifications/_spring/olatdefaultconfig.xml
index e62f85aa925..c767c84375e 100644
--- a/src/main/resources/serviceconfig/org/olat/notifications/_spring/olatdefaultconfig.xml
+++ b/src/main/resources/serviceconfig/org/olat/notifications/_spring/olatdefaultconfig.xml
@@ -30,7 +30,7 @@
 			As of OLAT 6.3 it's best to let the cronjob run every two hours since users can now choose how often 
 			they will get notified. The shortest interval is set to two hours. 	    	
 	    -->
-	    <property name="cronExpression" value="0 10 */2 * * ?" />
+	    <property name="cronExpression" value="${notification.cronjob.expression}" />
 
 		<!-- OLAT-5093 start delay ensures there's no conflict with server startup and db not being ready yet -->
 	    <property name="startDelay" value="300000" />
-- 
GitLab