From e0d90daec7fd5b26e478c3792ef20c8dc4e344e4 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 22 Apr 2015 18:47:44 +0200
Subject: [PATCH] OO-1522: refactor to folder worker poll in a thread pool
 executor with a very short queue and a CallerRunPolicy to slow down the main
 indexer if the executor is busy

---
 .../org/olat/search/_spring/searchContext.xml |  13 +--
 .../service/document/IdentityDocument.java    |   6 +-
 .../search/service/indexer/FolderIndexer.java | 102 ++--------------
 .../service/indexer/FolderIndexerWorker.java  |  62 +++-------
 .../indexer/FolderIndexerWorkerPool.java      | 110 ------------------
 .../service/indexer/FullIndexerStatus.java    |   4 +-
 .../olat/search/service/indexer/Index.java    |  10 +-
 .../search/service/indexer/LeafIndexer.java   |  90 ++++++++++++++
 .../service/indexer/OlatFullIndexer.java      |  65 +++++++----
 .../indexer/group/GroupFolderIndexer.java     |   1 +
 .../indexer/group/GroupWikiIndexer.java       |  27 ++---
 .../indexer/identity/IdentityIndexer.java     |  38 +++---
 .../indexer/identity/ProfileIndexer.java      |   2 +-
 .../indexer/identity/PublicFolderIndexer.java |   3 +-
 .../repository/ImsCPRepositoryIndexer.java    |  23 ++--
 .../course/BCCourseNodeIndexer.java           |  12 +-
 .../course/CPCourseNodeIndexer.java           |   2 +-
 .../course/DialogCourseNodeIndexer.java       |  31 ++---
 .../course/FOCourseNodeIndexer.java           |  15 ++-
 .../ProjectBrokerCourseNodeIndexer.java       |  22 ++--
 .../course/SPCourseNodeIndexer.java           |  61 +++++-----
 .../course/STCourseNodeIndexer.java           |  14 ++-
 .../course/TACourseNodeIndexer.java           |  60 +++++-----
 .../course/WikiCourseNodeIndexer.java         |  20 ++--
 24 files changed, 340 insertions(+), 453 deletions(-)
 delete mode 100644 src/main/java/org/olat/search/service/indexer/FolderIndexerWorkerPool.java
 create mode 100644 src/main/java/org/olat/search/service/indexer/LeafIndexer.java

diff --git a/src/main/java/org/olat/search/_spring/searchContext.xml b/src/main/java/org/olat/search/_spring/searchContext.xml
index d847b8cece5..3a433a78ea9 100644
--- a/src/main/java/org/olat/search/_spring/searchContext.xml
+++ b/src/main/java/org/olat/search/_spring/searchContext.xml
@@ -42,7 +42,7 @@
 		<property name="indexerCron" value="${search.indexing.cronjob}" />
 	</bean>
 	
-		<bean id="searchExecutor" class="org.springframework.core.task.support.ExecutorServiceAdapter">
+	<bean id="searchExecutor" class="org.springframework.core.task.support.ExecutorServiceAdapter">
 		<constructor-arg index="0" ref="searchSpringExecutor" />
 	</bean>
 	
@@ -115,9 +115,9 @@
 	       <property name="targetMethod" value="init" />
 	       <property name="arguments">
 	             <value>
-	        searchService=${search.service}
+					searchService=${search.service}
 	             
-	        generateIndexAtStartup=${generate.index.at.startup}		
+					generateIndexAtStartup=${generate.index.at.startup}		
 					tempIndexPath=${search.index.tempIndex}
 					tempSpellCheckPath=${search.index.tempSpellcheck}
 					pdfTextBufferPath=${search.index.pdfBuffer}
@@ -143,10 +143,9 @@
 					<!-- updater runs every xx ms (0=stopped) -->
 					<!-- The updater is NOT implemented for all index elements, do not use it for now! -->
 					updateInterval=0
-					ramBufferSizeMb=16
-					
-	          </value>
-	       </property>
+					ramBufferSizeMb=16		
+			</value>
+		</property>
 	</bean>
 
 	<!-- Indexer factory -->
diff --git a/src/main/java/org/olat/search/service/document/IdentityDocument.java b/src/main/java/org/olat/search/service/document/IdentityDocument.java
index 58220b533cd..1d15d9e71db 100644
--- a/src/main/java/org/olat/search/service/document/IdentityDocument.java
+++ b/src/main/java/org/olat/search/service/document/IdentityDocument.java
@@ -101,7 +101,5 @@ public class IdentityDocument extends OlatDocument {
 		
 		if (log.isDebug()) log.debug(identityDocument.toString());
 		return identityDocument.getLuceneDocument();
-	}
-
-	
-}
+	}	
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/search/service/indexer/FolderIndexer.java b/src/main/java/org/olat/search/service/indexer/FolderIndexer.java
index e8f6ebafc37..182d7689eab 100644
--- a/src/main/java/org/olat/search/service/indexer/FolderIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/FolderIndexer.java
@@ -25,19 +25,10 @@
 
 package org.olat.search.service.indexer;
 
-
 import java.io.IOException;
 
-import org.apache.lucene.document.Document;
-import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.persistence.DBFactory;
-import org.olat.core.util.WorkThreadInformations;
 import org.olat.core.util.vfs.VFSContainer;
-import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.search.service.SearchResourceContext;
-import org.olat.search.service.document.file.DocumentAccessException;
-import org.olat.search.service.document.file.FileDocumentFactory;
 
 /**
  * Common folder indexer. Index all files form a certain VFS-container as starting point.
@@ -47,92 +38,13 @@ public abstract class FolderIndexer extends AbstractHierarchicalIndexer {
 
 	protected void doIndexVFSContainer(SearchResourceContext parentResourceContext, VFSContainer container, OlatFullIndexer indexWriter, String filePath, FolderIndexerAccess accessRule)
 	throws IOException, InterruptedException {
-		if (FolderIndexerWorkerPool.getInstance().isDisabled()) {
-			// Do index in single thread mode
-			doIndexVFSContainerByMySelf(parentResourceContext, container, indexWriter, filePath, accessRule);
-		} else {
-			// Start new thread to index folder
-			FolderIndexerWorker runnableFolderIndexer = FolderIndexerWorkerPool.getInstance().getIndexer(); 
-			runnableFolderIndexer.setAccessRule(accessRule);
-			runnableFolderIndexer.setParentResourceContext(parentResourceContext);
-			runnableFolderIndexer.setContainer(container);
-			runnableFolderIndexer.setIndexWriter(indexWriter);
-			runnableFolderIndexer.setFilePath(filePath);
-			// Start Indexing from this rootContainer in an own thread 
-			runnableFolderIndexer.start();
-		}
-	}
-
-	// This index methods will be used in single-thread mode only (FolderIndexerWorkerPool is disabled)
-	//////////////////////////////////////////////////////////////////////////////////////////////////
-	private void doIndexVFSContainerByMySelf(SearchResourceContext parentResourceContext, VFSContainer container, OlatFullIndexer indexWriter, String filePath, FolderIndexerAccess accessRule)
-	throws IOException, InterruptedException {
-		// Items: List of VFSContainer & VFSLeaf
-		String myFilePath = filePath;
-		for (VFSItem item : container.getItems()) {
-			if (item instanceof VFSContainer) {
-				// ok it is a container go further
-				if (isLogDebugEnabled()) logDebug(item.getName() + " is a VFSContainer => go further ");
-				if(accessRule.allowed(item)) {
-					doIndexVFSContainerByMySelf(parentResourceContext, (VFSContainer)item, indexWriter, myFilePath + "/" + ((VFSContainer)item).getName(), accessRule);
-				}
-			} else if (item instanceof VFSLeaf) {
-				// ok it is a file => analyse it
-				if (isLogDebugEnabled()) logDebug(item.getName() + " is a VFSLeaf => analyse file");
-				if(accessRule.allowed(item)) {
-					doIndexVFSLeafByMySelf(parentResourceContext, (VFSLeaf)item, indexWriter, myFilePath);
-				}
-			} else {
-				logWarn("Unkown element in item-list class=" + item.getClass(), null);
-			}
-			// TODO:cg/27.10.2010		try to fix Indexer ERROR 'Overdue resource check-out stack trace.' on OLATNG
-			DBFactory.getInstance().commitAndCloseSession();
-		}
+		FolderIndexerWorker runnableFolderIndexer = new  FolderIndexerWorker();
+		runnableFolderIndexer.setAccessRule(accessRule);
+		runnableFolderIndexer.setParentResourceContext(parentResourceContext);
+		runnableFolderIndexer.setContainer(container);
+		runnableFolderIndexer.setIndexWriter(indexWriter);
+		runnableFolderIndexer.setFilePath(filePath);
+		indexWriter.submit(runnableFolderIndexer);
 	}
 	
-	protected void doIndexVFSLeafByMySelf(SearchResourceContext leafResourceContext, VFSLeaf leaf, OlatFullIndexer indexWriter, String filePath) throws InterruptedException {
-		if (isLogDebugEnabled()) logDebug("Analyse VFSLeaf=" + leaf.getName());
-		try {
-			if (CoreSpringFactory.getImpl(FileDocumentFactory.class).isFileSupported(leaf)) {
-				String myFilePath = "";
-				if (filePath.endsWith("/")) {
-					myFilePath = filePath + leaf.getName();
-				} else {
-	        myFilePath = filePath + "/" + leaf.getName();
-				}
-				leafResourceContext.setFilePath(myFilePath);
-				//fxdiff FXOLAT-97: high CPU load tracker
-				WorkThreadInformations.set("Index VFSLeaf=" + myFilePath + " at " + leafResourceContext.getResourceUrl());
-				Document document = CoreSpringFactory.getImpl(FileDocumentFactory.class).createDocument(leafResourceContext, leaf);
-	  		indexWriter.addDocument(document);
-			} else {
-				if (isLogDebugEnabled()) logDebug("Documenttype not supported. file=" + leaf.getName());
-			}
-		} catch (DocumentAccessException e) {
-			if (isLogDebugEnabled()) logDebug("Can not access document." + e.getMessage());
-		} catch (IOException ioEx) {
-			logWarn("IOException: Can not index leaf=" + leaf.getName(), ioEx);
-		} catch (InterruptedException iex) {
-			throw new InterruptedException(iex.getMessage());
-	  } catch (Exception ex) {
-			logWarn("Exception: Can not index leaf=" + leaf.getName(), ex);
-		//fxdiff FXOLAT-97: high CPU load tracker
-		} finally {
-  		WorkThreadInformations.unset();
-		}
-	}
-	
-	/**
-	 * @param leaf
-	 * @return Full file-path of leaf without leaf-name
-	 */
-	protected String getPathFor(VFSLeaf leaf) {
-		String path = "";
-		VFSContainer parentContainer = leaf.getParentContainer();
-		while (parentContainer.getParentContainer() != null) {
-			path = parentContainer.getName() + "/" + path;
-			parentContainer = parentContainer.getParentContainer();
-	  }
-		return path;
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java b/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java
index 2302f7989e8..a4f071e43e7 100644
--- a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java
+++ b/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java
@@ -27,6 +27,7 @@ package org.olat.search.service.indexer;
 
 
 import java.io.IOException;
+import java.util.concurrent.Callable;
 
 import org.apache.lucene.document.Document;
 import org.olat.core.CoreSpringFactory;
@@ -45,44 +46,28 @@ import org.olat.search.service.document.file.FileDocumentFactory;
  * Common folder indexer. Index all files form a certain VFS-container as starting point.
  * @author Christian Guretzki
  */
-public class FolderIndexerWorker implements Runnable{
+public class FolderIndexerWorker implements Callable<Boolean> {
 	
-	private static OLog log = Tracing.createLoggerFor(FolderIndexerWorker.class);
+	private static final OLog log = Tracing.createLoggerFor(FolderIndexerWorker.class);
 
-	public static final int STATE_RUNNING = 1;
-	public static final int STATE_FINISHED = 2;
-
-	private Thread folderIndexer = null;
-	
 	private SearchResourceContext parentResourceContext;
 	private VFSContainer container;
 	private OlatFullIndexer indexWriter;
 	private String filePath;
 	private FolderIndexerAccess accessRule;
 
-	private final String threadId;
-	
-	private int state = 0;
 	private final FileDocumentFactory docFactory;
 
-	public FolderIndexerWorker(int threadId) {
-		this.threadId = Integer.toString(threadId);
+	public FolderIndexerWorker() {
 		docFactory = CoreSpringFactory.getImpl(FileDocumentFactory.class);
 	}
 
-	public void start() {
-	  folderIndexer = new Thread(this, "folderIndexer-" + threadId );
-	  folderIndexer.setPriority(Thread.MIN_PRIORITY);
-	  folderIndexer.setDaemon(true);
-	  folderIndexer.start();
-	  state = STATE_RUNNING;
-	}
-	
-	public void run() {
-		try {
-			if (log.isDebug()) log.debug("folderIndexer-" + threadId + " run...");			
+	@Override
+	public Boolean call() throws Exception {
+		boolean allOk = false;
+		try {			
 			doIndexVFSContainer(parentResourceContext, container, indexWriter, filePath, accessRule);
-			if (log.isDebug()) log.debug("folderIndexer-" + threadId + " finished");
+			allOk = true;
 		} catch (IOException e) {
 			log.warn("IOException in run", e);
 		} catch (InterruptedException e) {
@@ -93,10 +78,8 @@ public class FolderIndexerWorker implements Runnable{
 		} finally {
 			//db session a saved in a thread local
 			DBFactory.getInstance().commitAndCloseSession();
-			FolderIndexerWorkerPool.getInstance().release(this);
 		}
-		log.debug("folderIndexer-" + threadId + " end of run");
-		state = STATE_FINISHED;
+		return allOk;
 	}
 	
 	protected void doIndexVFSContainer(SearchResourceContext resourceContext, VFSContainer cont, OlatFullIndexer writer, String fPath, FolderIndexerAccess aRule)
@@ -131,10 +114,10 @@ public class FolderIndexerWorker implements Runnable{
 				//fxdiff FXOLAT-97: high CPU load tracker
 				WorkThreadInformations.setInfoFiles(myFilePath, leaf);
 				WorkThreadInformations.set("Index VFSLeaf=" + myFilePath + " at " + leafResourceContext.getResourceUrl());
-  			Document document = docFactory.createDocument(leafResourceContext, leaf);
-  			if(document != null) {//document wihich are disabled return null
-  				writer.addDocument(document);
-  			}
+				Document document = docFactory.createDocument(leafResourceContext, leaf);
+				if(document != null) {//document which are disabled return null
+					writer.addDocument(document);
+				}
 			} else {
 				if (log.isDebug()) log.debug("Documenttype not supported. file=" + leaf.getName());
 			}
@@ -146,13 +129,10 @@ public class FolderIndexerWorker implements Runnable{
 			log.warn("IOException: Can not index leaf=" + leaf.getName(), ioEx);
 		} catch (Exception ex) {
 			log.warn("Exception: Can not index leaf=" + leaf.getName(), ex);
-		//fxdiff FXOLAT-97: high CPU load tracker
 		} finally {
 			WorkThreadInformations.unset();
 		}
 	}
-	
-
 
 	public void setParentResourceContext(SearchResourceContext newParentResourceContext) {
 		this.parentResourceContext = newParentResourceContext;
@@ -173,18 +153,4 @@ public class FolderIndexerWorker implements Runnable{
 	public void setAccessRule(FolderIndexerAccess accessRule) {
 		this.accessRule = accessRule;
 	}
-
-	public String getId() {
-		return threadId;
-	}
-
-	/**
-	 * @return Returns the state.
-	 */
-	public int getState() {
-		if ((folderIndexer != null) && folderIndexer.isAlive()) {
-			return STATE_RUNNING;
-		}
-		return state;
-	}
 }
diff --git a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorkerPool.java b/src/main/java/org/olat/search/service/indexer/FolderIndexerWorkerPool.java
deleted file mode 100644
index c47b11cbe30..00000000000
--- a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorkerPool.java
+++ /dev/null
@@ -1,110 +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.indexer;
-
-
-import java.util.List;
-import java.util.Vector;
-
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
-import org.olat.search.service.SearchServiceFactory;
-
-/**
- * Pool of folder indexer worker. 
- * @author Christian Guretzki
- */
-public class FolderIndexerWorkerPool {
-  private OLog log = Tracing.createLoggerFor(FolderIndexerWorkerPool.class);
-  private int poolSize = 10;
-  private static FolderIndexerWorkerPool instance = null;
-  private List<FolderIndexerWorker> indexerList;
-  private List<FolderIndexerWorker> runningIndexerList;
-
-	/** 
-	 * 
-	 */
-  public FolderIndexerWorkerPool() {
-  	poolSize = SearchServiceFactory.getService().getSearchModuleConfig().getFolderPoolSize();
-  	indexerList = new Vector<FolderIndexerWorker>();
-  	// 
-  	for (int i = 0; i < poolSize; i++) {
-  		indexerList.add(new FolderIndexerWorker(i));	
-		}
-  	runningIndexerList = new Vector<FolderIndexerWorker>(); 	
-  }
-    
-	protected static FolderIndexerWorkerPool getInstance() {
-		if (instance == null) {
-			instance = new FolderIndexerWorkerPool();
-		}
-		return instance;
-	}
-
-	public void release(FolderIndexerWorker indexer) {
-		if (log.isDebug()) log.debug("indexer=" + indexer.getId() + " released");
-    runningIndexerList.remove(indexer);
-		int id = Integer.parseInt(indexer.getId());
-		indexerList.add(new FolderIndexerWorker(id));
-		if (log.isDebug()) log.debug("Available indexer=" + indexerList.size() + " Running indexer=" + runningIndexerList.size());
-	}
-
-  public FolderIndexerWorker getIndexer( ) {
-		while(indexerList.isEmpty()) {
-			try {
-				Thread.sleep(1000);
-			} catch (InterruptedException e) {
-				//
-			}
-		}
-		FolderIndexerWorker freeFolderIndexer = indexerList.remove(0);
-		runningIndexerList.add(freeFolderIndexer);
-		if (log.isDebug()) log.debug("indexer=" + freeFolderIndexer.getId() + " selected");
-		if (log.isDebug()) log.debug("Available indexer=" + indexerList.size() + " Running indexer=" + runningIndexerList.size());
-		return freeFolderIndexer;
-	}
-	
-	/**
-	 * 
-	 * @return  Return true if any indexer is still running
-	 */
-	public boolean isIndexerRunning() {
-		return indexerList.size() < this.poolSize;
-	}
-	
-	public int getNumberOfRunningIndexer() {
-		return runningIndexerList.size();
-	}
-	
-	public int getNumberOfAvailableIndexer() {
-		return indexerList.size();
-	}
-	
-	public boolean isDisabled() {
-		return poolSize == 0;
-	}
-	
-}
diff --git a/src/main/java/org/olat/search/service/indexer/FullIndexerStatus.java b/src/main/java/org/olat/search/service/indexer/FullIndexerStatus.java
index 57cf3c24469..366005d7d77 100644
--- a/src/main/java/org/olat/search/service/indexer/FullIndexerStatus.java
+++ b/src/main/java/org/olat/search/service/indexer/FullIndexerStatus.java
@@ -190,7 +190,7 @@ public class FullIndexerStatus {
 	 * @return Returns the runningFolderIndexer.
 	 */
 	public int getNumberRunningFolderIndexer() {
-		return FolderIndexerWorkerPool.getInstance().getNumberOfRunningIndexer();
+		return -1;//FolderIndexerWorkerPool.getInstance().getNumberOfRunningIndexer();
 	}
 
 
@@ -198,7 +198,7 @@ public class FullIndexerStatus {
 	 * @return Returns the availableFolderIndexer.
 	 */
 	public int getNumberAvailableFolderIndexer() {
-		return FolderIndexerWorkerPool.getInstance().getNumberOfAvailableIndexer();
+		return -1;//FolderIndexerWorkerPool.getInstance().getNumberOfAvailableIndexer();
 	}
 
 
diff --git a/src/main/java/org/olat/search/service/indexer/Index.java b/src/main/java/org/olat/search/service/indexer/Index.java
index 87668bad24d..25db3b52e23 100644
--- a/src/main/java/org/olat/search/service/indexer/Index.java
+++ b/src/main/java/org/olat/search/service/indexer/Index.java
@@ -63,15 +63,15 @@ public class Index {
 	 * @param restartInterval Restart interval of full-index in milliseconds.
 	 * @param indexInterval   Sleeping time in milliseconds between adding documents to index.
 	 */
-	public Index(SearchModule searchModuleConfig, SearchSpellChecker spellChecker, MainIndexer mainIndexer,
+	public Index(SearchModule searchModule, SearchSpellChecker spellChecker, MainIndexer mainIndexer,
 			LifeFullIndexer lifeIndexer, CoordinatorManager coordinatorManager) {
 		this.spellChecker = spellChecker;
-		this.indexPath = searchModuleConfig.getFullIndexPath();
-		this.tempIndexPath = searchModuleConfig.getFullTempIndexPath();
-		this.permanentIndexPath = searchModuleConfig.getFullPermanentIndexPath();
+		this.indexPath = searchModule.getFullIndexPath();
+		this.tempIndexPath = searchModule.getFullTempIndexPath();
+		this.permanentIndexPath = searchModule.getFullPermanentIndexPath();
 		this.lifeIndexer = lifeIndexer;
 		
-		fullIndexer = new OlatFullIndexer(this, searchModuleConfig, mainIndexer, coordinatorManager);
+		fullIndexer = new OlatFullIndexer(this, searchModule, mainIndexer, coordinatorManager);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/search/service/indexer/LeafIndexer.java b/src/main/java/org/olat/search/service/indexer/LeafIndexer.java
new file mode 100644
index 00000000000..ce1ca25ecf4
--- /dev/null
+++ b/src/main/java/org/olat/search/service/indexer/LeafIndexer.java
@@ -0,0 +1,90 @@
+/**
+* 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.indexer;
+
+
+import java.io.IOException;
+
+import org.apache.lucene.document.Document;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.util.WorkThreadInformations;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.search.service.SearchResourceContext;
+import org.olat.search.service.document.file.DocumentAccessException;
+import org.olat.search.service.document.file.FileDocumentFactory;
+
+/**
+ * Common folder indexer. Index all files form a certain VFS-container as starting point.
+ * @author Christian Guretzki
+ */
+public abstract class LeafIndexer extends AbstractHierarchicalIndexer {
+
+	protected void doIndexVFSLeafByMySelf(SearchResourceContext leafResourceContext, VFSLeaf leaf, OlatFullIndexer indexWriter, String filePath) throws InterruptedException {
+		if (isLogDebugEnabled()) logDebug("Analyse VFSLeaf=" + leaf.getName());
+		try {
+			if (CoreSpringFactory.getImpl(FileDocumentFactory.class).isFileSupported(leaf)) {
+				String myFilePath = "";
+				if (filePath.endsWith("/")) {
+					myFilePath = filePath + leaf.getName();
+				} else {
+					myFilePath = filePath + "/" + leaf.getName();
+				}
+				leafResourceContext.setFilePath(myFilePath);
+
+				WorkThreadInformations.set("Index VFSLeaf=" + myFilePath + " at " + leafResourceContext.getResourceUrl());
+				Document document = CoreSpringFactory.getImpl(FileDocumentFactory.class).createDocument(leafResourceContext, leaf);
+				indexWriter.addDocument(document);
+			} else {
+				if (isLogDebugEnabled()) logDebug("Documenttype not supported. file=" + leaf.getName());
+			}
+		} catch (DocumentAccessException e) {
+			if (isLogDebugEnabled()) logDebug("Can not access document." + e.getMessage());
+		} catch (IOException ioEx) {
+			logWarn("IOException: Can not index leaf=" + leaf.getName(), ioEx);
+		} catch (InterruptedException iex) {
+			throw new InterruptedException(iex.getMessage());
+		} catch (Exception ex) {
+			logWarn("Exception: Can not index leaf=" + leaf.getName(), ex);
+		} finally {
+			WorkThreadInformations.unset();
+		}
+	}
+	
+	/**
+	 * @param leaf
+	 * @return Full file-path of leaf without leaf-name
+	 */
+	protected String getPathFor(VFSLeaf leaf) {
+		String path = "";
+		VFSContainer parentContainer = leaf.getParentContainer();
+		while (parentContainer.getParentContainer() != null) {
+			path = parentContainer.getName() + "/" + path;
+			parentContainer = parentContainer.getParentContainer();
+		}
+		return path;
+	}
+}
\ No newline at end of file
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 f0006595a8c..fbe94ac7127 100644
--- a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
@@ -73,9 +73,10 @@ public class OlatFullIndexer {
 	
 	private static final OLog log = Tracing.createLoggerFor(OlatFullIndexer.class);
 	private static final int INDEX_MERGE_FACTOR = 1000;
+	private static final int MAX_WAITING_COUNT = 600;// = 10Min
 
-	private String  indexPath;
-	private String  tempIndexPath;
+	private String indexPath;
+	private String tempIndexPath;
 
 	/**
 	 * Reference to indexer for done callback.
@@ -90,6 +91,8 @@ public class OlatFullIndexer {
 
 	private double ramBufferSizeMB;
 	
+	private final int indexerPoolSize;
+	
 	/** Current status of full-indexer. */
 	private FullIndexerStatus fullIndexerStatus;
 
@@ -105,13 +108,13 @@ public class OlatFullIndexer {
 	private Map<String,Integer> documentCounters;
 	private Map<String,Integer> fileTypeCounters;
 
-	private MainIndexer mainIndexer;
-	private CoordinatorManager coordinatorManager;
+	private final MainIndexer mainIndexer;
+	private final CoordinatorManager coordinatorManager;
 
 	private static final Object indexerWriterBlock = new Object();
+	private ThreadPoolExecutor indexerExecutor;
 	private ThreadPoolExecutor indexerWriterExecutor;
 
-	
 	/**
 	 * 
 	 * @param tempIndexPath   Absolute file path to temporary index directory.
@@ -119,16 +122,21 @@ public class OlatFullIndexer {
 	 * @param restartInterval Restart interval in milliseconds.
 	 * @param indexInterval   Sleep time in milliseconds between adding documents.
 	 */
-	public OlatFullIndexer(Index index, SearchModule searchModuleConfig,
+	public OlatFullIndexer(Index index, SearchModule searchModule,
 			MainIndexer mainIndexer, CoordinatorManager coordinatorManager) {
 		this.index = index;
 		this.mainIndexer = mainIndexer;
 		this.coordinatorManager = coordinatorManager;
-		indexPath = searchModuleConfig.getFullIndexPath();
-		tempIndexPath = searchModuleConfig.getFullTempIndexPath();
-		indexInterval = searchModuleConfig.getIndexInterval();
-		documentsPerInterval = searchModuleConfig.getDocumentsPerInterval();
-		ramBufferSizeMB = searchModuleConfig.getRAMBufferSizeMB();
+		if(searchModule.getFolderPoolSize() <= 2) {
+			indexerPoolSize = 1;
+		} else {
+			indexerPoolSize = searchModule.getFolderPoolSize() - 1;
+		}
+		indexPath = searchModule.getFullIndexPath();
+		tempIndexPath = searchModule.getFullTempIndexPath();
+		indexInterval = searchModule.getIndexInterval();
+		documentsPerInterval = searchModule.getDocumentsPerInterval();
+		ramBufferSizeMB = searchModule.getRAMBufferSizeMB();
 		fullIndexerStatus = new FullIndexerStatus(1);
 		stopIndexing = true;
 		initStatus();
@@ -141,6 +149,7 @@ public class OlatFullIndexer {
 			final AtomicLong last = new AtomicLong(1);
 			try {
 				Files.walkFileTree(indexDir.toPath(), new SimpleFileVisitor<Path>(){
+					@Override
 					public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 						if(attrs.isRegularFile()) {
 							FileTime time = attrs.lastModifiedTime();
@@ -210,7 +219,12 @@ public class OlatFullIndexer {
 	private void doIndex() throws InterruptedException{
 		try {
 			WorkThreadInformations.setLongRunningTask("indexer");
-
+			
+			if(indexerExecutor == null) {
+				BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(2);
+				indexerExecutor = new ThreadPoolExecutor(indexerPoolSize, indexerPoolSize, 0L, TimeUnit.MILLISECONDS,
+						queue, new ThreadPoolExecutor.CallerRunsPolicy());
+			}
 			if(indexerWriterExecutor == null) {
 				BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(2);
 				indexerWriterExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, queue);
@@ -224,22 +238,18 @@ public class OlatFullIndexer {
 			SearchResourceContext searchResourceContext = new SearchResourceContext();
 			log.info("doIndex start. OlatFullIndexer with Debug output");
 			mainIndexer.doIndex(searchResourceContext, null /*no parent*/, this);
+			DBFactory.getInstance().commitAndCloseSession();
 	
 			log.info("Wait until every folder indexer is finished");
 			
+			indexerExecutor.shutdown();
+			indexerExecutor.awaitTermination(10, TimeUnit.MINUTES);
 			DBFactory.getInstance().commitAndCloseSession();
-			// check if every folder indexer is finished max waiting-time 10Min (=waitingCount-limit = 60) 
-			int waitingCount = 0;
-			int MAX_WAITING_COUNT = 60;// = 10Min
-			while (FolderIndexerWorkerPool.getInstance().isIndexerRunning() && (waitingCount++ < MAX_WAITING_COUNT) ) { 
-				Thread.sleep(10000);
-			}
-			if (waitingCount >= MAX_WAITING_COUNT) log.info("Finished with max waiting time!");
 			
 
 			log.info("Wait until index writer executor is finished");
 			int waitWriter = 0;
-			while (indexerWriterExecutor.getActiveCount() > 0 && (waitWriter++ < (10 * MAX_WAITING_COUNT))) { 
+			while (indexerWriterExecutor.getActiveCount() > 0 && (waitWriter++ < MAX_WAITING_COUNT)) { 
 				Thread.sleep(1000);
 			}
 			
@@ -256,12 +266,25 @@ public class OlatFullIndexer {
 			DBFactory.getInstance().commitAndCloseSession();
 			log.debug("doIndex: commit & close session");
 			
+			if(indexerExecutor != null) {
+				indexerExecutor.shutdownNow();
+				indexerExecutor = null;
+			}
 			if(indexerWriterExecutor != null) {
 				indexerWriterExecutor.shutdownNow();
 				indexerWriterExecutor = null;
 			}
 		}
 	}
+	
+	public Future<Boolean> submit(Callable<Boolean> task) {
+		if(indexerExecutor != null && !indexerExecutor.isShutdown()) {
+			return indexerExecutor.submit(task);
+		} else {
+			log.error("Try to submit a task to index executor but it's closed.");
+			return null;
+		}
+	}
 
 	/**
 	 * 
@@ -302,8 +325,10 @@ public class OlatFullIndexer {
 				// no logging available (shut down) => do nothing
 			}
 		}
+		
 		fullIndexerStatus.setStatus(FullIndexerStatus.STATUS_STOPPED);
 		stopIndexing = true;
+		
 		try {
 			log.info("quit indexing run.");
 		} catch (NullPointerException nex) {
diff --git a/src/main/java/org/olat/search/service/indexer/group/GroupFolderIndexer.java b/src/main/java/org/olat/search/service/indexer/group/GroupFolderIndexer.java
index 93c3f1ce51e..47b43f97e72 100644
--- a/src/main/java/org/olat/search/service/indexer/group/GroupFolderIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/group/GroupFolderIndexer.java
@@ -59,6 +59,7 @@ public class GroupFolderIndexer extends FolderIndexer{
 		this.collaborationManager = collaborationManager;
 	}
 
+	@Override
 	public void doIndex(SearchResourceContext parentResourceContext, Object businessObj, OlatFullIndexer indexWriter) throws IOException,InterruptedException {
 		if (!(businessObj instanceof BusinessGroup) )
 			throw new AssertException("businessObj must be BusinessGroup");
diff --git a/src/main/java/org/olat/search/service/indexer/group/GroupWikiIndexer.java b/src/main/java/org/olat/search/service/indexer/group/GroupWikiIndexer.java
index 5445e7b583e..e764c50e7bc 100644
--- a/src/main/java/org/olat/search/service/indexer/group/GroupWikiIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/group/GroupWikiIndexer.java
@@ -40,22 +40,23 @@ import org.olat.modules.wiki.WikiManager;
 import org.olat.modules.wiki.WikiPage;
 import org.olat.search.service.SearchResourceContext;
 import org.olat.search.service.document.WikiPageDocument;
-import org.olat.search.service.indexer.FolderIndexer;
+import org.olat.search.service.indexer.AbstractHierarchicalIndexer;
 import org.olat.search.service.indexer.OlatFullIndexer;
 
 /**
  * Index all group folders.
  * @author Christian Guretzki
  */
-public class GroupWikiIndexer extends FolderIndexer{
+public class GroupWikiIndexer extends AbstractHierarchicalIndexer {
   //Must correspond with LocalString_xx.properties
 	// Do not use '_' because we want to seach for certain documenttype and lucene haev problems with '_' 
 	public static final String TYPE = "type.group.wiki";
 
 	@Override
 	public void doIndex(SearchResourceContext parentResourceContext, Object businessObj, OlatFullIndexer indexWriter) throws IOException,InterruptedException {
-		if (!(businessObj instanceof BusinessGroup) )
+		if (!(businessObj instanceof BusinessGroup))
 			throw new AssertException("businessObj must be BusinessGroup");
+		
 		BusinessGroup businessGroup = (BusinessGroup)businessObj;
 		
 		// Index Group Wiki
@@ -64,17 +65,17 @@ public class GroupWikiIndexer extends FolderIndexer{
 		if (collabTools.isToolEnabled(CollaborationTools.TOOL_WIKI) ) {
 			try {
 				Wiki wiki = WikiManager.getInstance().getOrLoadWiki(businessGroup);
-					// loop over all wiki pages
-					List<WikiPage> wikiPageList = wiki.getAllPagesWithContent();
-					for (WikiPage wikiPage : wikiPageList) {
-					  SearchResourceContext wikiResourceContext = new SearchResourceContext(parentResourceContext);
-					  wikiResourceContext.setBusinessControlFor(BusinessGroupMainRunController.ORES_TOOLWIKI);
-					  wikiResourceContext.setDocumentType(TYPE);
-					  wikiResourceContext.setFilePath(wikiPage.getPageName());
+				// loop over all wiki pages
+				List<WikiPage> wikiPageList = wiki.getAllPagesWithContent();
+				for (WikiPage wikiPage : wikiPageList) {
+					SearchResourceContext wikiResourceContext = new SearchResourceContext(parentResourceContext);
+					wikiResourceContext.setBusinessControlFor(BusinessGroupMainRunController.ORES_TOOLWIKI);
+					wikiResourceContext.setDocumentType(TYPE);
+					wikiResourceContext.setFilePath(wikiPage.getPageName());
 			
-						Document document = WikiPageDocument.createDocument(wikiResourceContext, wikiPage);
-					  indexWriter.addDocument(document);
-					}
+					Document document = WikiPageDocument.createDocument(wikiResourceContext, wikiPage);
+					indexWriter.addDocument(document);
+				}
 			} catch (NullPointerException nex) {
 				logWarn("NullPointerException in GroupWikiIndexer.doIndex.", nex);
 			}
diff --git a/src/main/java/org/olat/search/service/indexer/identity/IdentityIndexer.java b/src/main/java/org/olat/search/service/indexer/identity/IdentityIndexer.java
index ef16df93bf8..10245ea97c1 100644
--- a/src/main/java/org/olat/search/service/indexer/identity/IdentityIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/identity/IdentityIndexer.java
@@ -53,32 +53,26 @@ public class IdentityIndexer extends AbstractHierarchicalIndexer {
 	}
 
 	@Override
-	public void doIndex(SearchResourceContext parentResourceContext, Object parentObject, OlatFullIndexer indexWriter) throws IOException,
-			InterruptedException {
+	public void doIndex(SearchResourceContext parentResourceContext, Object parentObject, OlatFullIndexer indexWriter)
+	throws IOException, InterruptedException {
 		
-  	int counter = 0;
-  	BaseSecurity secMgr = BaseSecurityManager.getInstance();
-  	List<Identity> identities = secMgr.getIdentitiesByPowerSearch(null, null, true, null, null, null, null, null, null, null, Identity.STATUS_ACTIV);
-  	if (isLogDebugEnabled()) logDebug("Found " + identities.size() + " active identities to index");
-
-  	// committing here to make sure the loadBusinessGroup below does actually
-  	// reload from the database and not only use the session cache 
-  	// (see org.hibernate.Session.get(): 
-  	//  If the instance, or a proxy for the instance, is already associated with the session, return that instance or proxy.)
-  	DBFactory.getInstance().commitAndCloseSession();
+		int counter = 0;
+		BaseSecurity secMgr = BaseSecurityManager.getInstance();
+		List<Identity> identities = secMgr.getIdentitiesByPowerSearch(null, null, true, null, null, null, null, null, null, null, Identity.STATUS_ACTIV);
+		if (isLogDebugEnabled()) logDebug("Found " + identities.size() + " active identities to index");
+		DBFactory.getInstance().commitAndCloseSession();
   	
-  	for (Identity identity : identities) {
-  		try {
-				// reload the businessGroup here before indexing it to make sure it has not been deleted in the meantime
-  			Identity reloadedIdentity = secMgr.findIdentityByName(identity.getName());
-				if (reloadedIdentity==null || (reloadedIdentity.getStatus()>=Identity.STATUS_VISIBLE_LIMIT)) {
+		for (Identity identity : identities) {
+			try {
+				// reload the identity here before indexing it to make sure it has not been deleted in the meantime
+				identity = secMgr.findIdentityByName(identity.getName());
+				if (identity == null || (identity.getStatus()>=Identity.STATUS_VISIBLE_LIMIT)) {
 					logInfo("doIndex: identity was deleted while we were indexing. The deleted identity was: "+identity);
 					continue;
 				}
-				identity = reloadedIdentity;
 
-  			if (isLogDebugEnabled()) logDebug("Indexing identity::" + identity.getName() + " and counter::" + counter);  	  	
-  	  	// Create a search context for this identity. The search context will open the users visiting card in a new tab
+				if (isLogDebugEnabled()) logDebug("Indexing identity::" + identity.getName() + " and counter::" + counter);  	  	
+				// Create a search context for this identity. The search context will open the users visiting card in a new tab
 				SearchResourceContext searchResourceContext = new SearchResourceContext(parentResourceContext);
 				searchResourceContext.setBusinessControlFor(OresHelper.createOLATResourceableInstance(Identity.class, identity.getKey()));
 				searchResourceContext.setParentContextType(TYPE);
@@ -88,12 +82,12 @@ public class IdentityIndexer extends AbstractHierarchicalIndexer {
 					indexer.doIndex(searchResourceContext, identity, indexWriter);
 				}
 				
-  			counter++;
+				counter++;
 			} catch (Exception ex) {
 				logWarn("Exception while indexing identity::" + identity.getName() + ". Skipping this user, try next one.", ex);
 				DBFactory.getInstance(false).rollbackAndCloseSession();
 			}
 		}
-  	if (isLogDebugEnabled()) logDebug("IdentityIndexer finished with counter::" + counter);
+		if (isLogDebugEnabled()) logDebug("IdentityIndexer finished with counter::" + counter);
 	}
 }
diff --git a/src/main/java/org/olat/search/service/indexer/identity/ProfileIndexer.java b/src/main/java/org/olat/search/service/indexer/identity/ProfileIndexer.java
index 641ea2a04b1..7a6249e1676 100644
--- a/src/main/java/org/olat/search/service/indexer/identity/ProfileIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/identity/ProfileIndexer.java
@@ -42,6 +42,7 @@ public class ProfileIndexer extends AbstractHierarchicalIndexer {
 	/**
 	 * @see org.olat.search.service.indexer.Indexer#getSupportedTypeName()
 	 */
+	@Override
 	public String getSupportedTypeName() {
 		return Identity.class.getSimpleName();
 	}
@@ -62,6 +63,5 @@ public class ProfileIndexer extends AbstractHierarchicalIndexer {
 			logWarn("Exception while indexing profile for identity::" + parentObject.toString() + ". Skipping this user, try next one.", ex);
 		}
 		if (isLogDebugEnabled()) logDebug("ProfileIndexer finished for user::" + parentObject.toString());
-
 	}
 }
diff --git a/src/main/java/org/olat/search/service/indexer/identity/PublicFolderIndexer.java b/src/main/java/org/olat/search/service/indexer/identity/PublicFolderIndexer.java
index c37ed9661fb..1a8a94f05a0 100644
--- a/src/main/java/org/olat/search/service/indexer/identity/PublicFolderIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/identity/PublicFolderIndexer.java
@@ -45,6 +45,7 @@ public class PublicFolderIndexer extends FolderIndexer {
 	/**
 	 * @see org.olat.search.service.indexer.Indexer#getSupportedTypeName()
 	 */
+	@Override
 	public String getSupportedTypeName() {
 		return Identity.class.getSimpleName();
 	}
@@ -52,7 +53,7 @@ public class PublicFolderIndexer extends FolderIndexer {
 	/**
 	 * @see org.olat.repository.handlers.RepositoryHandler#supportsDownload()
 	 */
-
+	@Override
 	public void doIndex(SearchResourceContext parentResourceContext, Object parentObject, OlatFullIndexer indexWriter) {
 
 		try {
diff --git a/src/main/java/org/olat/search/service/indexer/repository/ImsCPRepositoryIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/ImsCPRepositoryIndexer.java
index 5a7543f69c7..380584886ff 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/ImsCPRepositoryIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/ImsCPRepositoryIndexer.java
@@ -28,7 +28,6 @@ package org.olat.search.service.indexer.repository;
 import java.io.File;
 import java.io.IOException;
 
-import org.olat.core.logging.AssertException;
 import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.fileresource.FileResourceManager;
@@ -49,10 +48,8 @@ public class ImsCPRepositoryIndexer extends FolderIndexer {
 	public final static String TYPE = "type.repository.entry.imscp";
 
 	public final static String ORES_TYPE_CP = ImsCPFileResource.TYPE_NAME;
-	
-	/**
-	 * 
-	 */
+
+	@Override
 	public String getSupportedTypeName() {	
 		return ORES_TYPE_CP; 
 	}
@@ -64,15 +61,15 @@ public class ImsCPRepositoryIndexer extends FolderIndexer {
 	public void doIndex(SearchResourceContext resourceContext, Object parentObject, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
 		RepositoryEntry repositoryEntry = (RepositoryEntry) parentObject;
 		if (isLogDebugEnabled()) logDebug("Analyse IMS CP RepositoryEntry...");
-
 		resourceContext.setDocumentType(TYPE);
     
-		if (repositoryEntry == null) throw new AssertException("no Repository");
-    File cpRoot = FileResourceManager.getInstance().unzipFileResource(repositoryEntry.getOlatResource());
-		if (cpRoot == null) throw new AssertException("file of repository entry " + repositoryEntry.getKey() + "was missing");
-
-		SearchResourceContext cpContext = new SearchResourceContext(resourceContext);
-    VFSContainer rootContainer = new LocalFolderImpl(cpRoot);
-    doIndexVFSContainer(cpContext, rootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+		if (repositoryEntry != null) {
+			File cpRoot = FileResourceManager.getInstance().unzipFileResource(repositoryEntry.getOlatResource());
+			if (cpRoot != null) {
+				SearchResourceContext cpContext = new SearchResourceContext(resourceContext);
+				VFSContainer rootContainer = new LocalFolderImpl(cpRoot);
+				doIndexVFSContainer(cpContext, rootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+			}
+		}
 	}
 }
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/BCCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/BCCourseNodeIndexer.java
index c26cf35458f..179854487ce 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/BCCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/BCCourseNodeIndexer.java
@@ -48,19 +48,21 @@ public class BCCourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 
 	private final static String SUPPORTED_TYPE_NAME = "org.olat.course.nodes.BCCourseNode";
 	
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
 		if (isLogDebugEnabled()) logDebug("Index Briefcase..." );
 
-    SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-    courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setDocumentType(TYPE);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
+    	courseNodeResourceContext.setBusinessControlFor(courseNode);
+    	courseNodeResourceContext.setDocumentType(TYPE);
+    	courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+    	courseNodeResourceContext.setDescription(courseNode.getLongTitle());
 
 		OlatNamedContainerImpl namedContainer = BCCourseNode.getNodeFolderContainer((BCCourseNode) courseNode, course.getCourseEnvironment());
 		doIndexVFSContainer(courseNodeResourceContext,namedContainer,indexWriter,"", FolderIndexerAccess.FULL_ACCESS);
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/CPCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/CPCourseNodeIndexer.java
index b6740ac4f5a..45f2d2439a9 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/CPCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/CPCourseNodeIndexer.java
@@ -66,7 +66,7 @@ public class CPCourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 	    	File cpRoot = FileResourceManager.getInstance().unzipFileResource(re.getOlatResource());
 	    	if(cpRoot != null) {
 	    		VFSContainer rootContainer = new LocalFolderImpl(cpRoot);
-	    		doIndexVFSContainer(courseNodeResourceContext,rootContainer,indexWriter,"", FolderIndexerAccess.FULL_ACCESS);
+	    		doIndexVFSContainer(courseNodeResourceContext, rootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
 	    	}
 	    }
 	}
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java
index fed0cfdd8a1..c6142f03e09 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java
@@ -80,14 +80,15 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 		//
 	}
 
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
-    SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-    courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
+		courseNodeResourceContext.setBusinessControlFor(courseNode);
+		courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+		courseNodeResourceContext.setDescription(courseNode.getLongTitle());
     
-    CoursePropertyManager coursePropMgr = course.getCourseEnvironment().getCoursePropertyManager();
-    DialogElementsPropertyManager dialogElmsMgr = DialogElementsPropertyManager.getInstance();
+		CoursePropertyManager coursePropMgr = course.getCourseEnvironment().getCoursePropertyManager();
+		DialogElementsPropertyManager dialogElmsMgr = DialogElementsPropertyManager.getInstance();
 		DialogPropertyElements elements = dialogElmsMgr.findDialogElements(coursePropMgr, courseNode);
 		List<DialogElement> list = new ArrayList<DialogElement>();
 		if (elements != null) list = elements.getDialogPropertyElements();
@@ -96,7 +97,7 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 			DialogElement element = iter.next();
 			element.getAuthor();
 			element.getDate();
-		  Forum forum = ForumManager.getInstance().loadForum(element.getForumKey());
+			Forum forum = ForumManager.getInstance().loadForum(element.getForumKey());
 			// do IndexForum
 			doIndexAllMessages(courseNodeResourceContext, forum, indexWriter );
 			// do Index File
@@ -121,10 +122,10 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 			if (CoreSpringFactory.getImpl(FileDocumentFactory.class).isFileSupported(leaf)) {
 				leafResourceContext.setFilePath(filename);
 				leafResourceContext.setDocumentType(TYPE_FILE);
-				//fxdiff FXOLAT-97: high CPU load tracker
+				
 				WorkThreadInformations.set("Index Dialog VFSLeaf=" + filename + " at " + leafResourceContext.getResourceUrl());
-  			Document document = CoreSpringFactory.getImpl(FileDocumentFactory.class).createDocument(leafResourceContext, leaf);
-	  		indexWriter.addDocument(document);
+				Document document = CoreSpringFactory.getImpl(FileDocumentFactory.class).createDocument(leafResourceContext, leaf);
+				indexWriter.addDocument(document);
 			} else {
 				if (isLogDebugEnabled()) logDebug("Documenttype not supported. file=" + leaf.getName());
 			}
@@ -134,11 +135,10 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 			logWarn("IOException: Can not index leaf=" + leaf.getName(), ioEx);
 		} catch (InterruptedException iex) {
 			throw new InterruptedException(iex.getMessage());
-	  } catch (Exception ex) {
+		} catch (Exception ex) {
 			logWarn("Exception: Can not index leaf=" + leaf.getName(), ex);
-		//fxdiff FXOLAT-97: high CPU load tracker
 		} finally {
-  		WorkThreadInformations.unset();
+			WorkThreadInformations.unset();
 		}
 	}
 
@@ -154,10 +154,12 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 		}
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
 	
+	@Override
 	public boolean checkAccess(ContextEntry contextEntry, BusinessControl businessControl, Identity identity, Roles roles)  {
 		ContextEntry ce = businessControl.popLauncherContextEntry();
 		OLATResourceable ores = ce.getOLATResourceable();
@@ -175,7 +177,7 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 			}
 			boolean isMessageHidden = Status.getStatus(threadtop.getStatusCode()).isHidden(); 
 			//assumes that if is owner then is moderator so it is allowed to see the hidden forum threads		
-	    //TODO: (LD) fix this!!! - the contextEntry is not the right context for this check
+			//TODO: (LD) fix this!!! - the contextEntry is not the right context for this check
 			boolean isOwner = BaseSecurityManager.getInstance().isIdentityPermittedOnResourceable(identity, Constants.PERMISSION_ACCESS,  contextEntry.getOLATResourceable());
 			if(isMessageHidden && !isOwner) {
 				return false;
@@ -186,5 +188,4 @@ public class DialogCourseNodeIndexer extends DefaultIndexer implements CourseNod
 			return false;
 		}
 	}
-	
 }
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/FOCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/FOCourseNodeIndexer.java
index af7bb9230c8..fd11d7357f8 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/FOCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/FOCourseNodeIndexer.java
@@ -59,15 +59,16 @@ public class FOCourseNodeIndexer extends ForumIndexer implements CourseNodeIndex
 	public final static String TYPE = "type.course.node.forum.message";
 
 	private final static String SUPPORTED_TYPE_NAME = "org.olat.course.nodes.FOCourseNode";
-	
+
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) {
 		try {
 			SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-	    courseNodeResourceContext.setBusinessControlFor(courseNode);
-	    courseNodeResourceContext.setDocumentType(TYPE);
-	    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-	    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
-	    doIndexForum(courseNodeResourceContext, course, courseNode, indexWriter);
+			courseNodeResourceContext.setBusinessControlFor(courseNode);
+			courseNodeResourceContext.setDocumentType(TYPE);
+			courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+			courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+			doIndexForum(courseNodeResourceContext, course, courseNode, indexWriter);
 		} catch(Exception ex) {
 			log.error("Exception indexing courseNode=" + courseNode, ex);
 		} catch (Error err) {
@@ -75,10 +76,12 @@ public class FOCourseNodeIndexer extends ForumIndexer implements CourseNodeIndex
 		}
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
 	
+	@Override
 	public boolean checkAccess(ContextEntry contextEntry, BusinessControl businessControl, Identity identity, Roles roles) {
 		ContextEntry ce = businessControl.popLauncherContextEntry();
 		Long resourceableId = ce.getOLATResourceable().getResourceableId();
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/ProjectBrokerCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/ProjectBrokerCourseNodeIndexer.java
index 2388a97ca2d..a0e3052f7d1 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/ProjectBrokerCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/ProjectBrokerCourseNodeIndexer.java
@@ -40,28 +40,29 @@ import org.olat.course.nodes.projectbroker.service.ProjectBrokerManager;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.search.service.SearchResourceContext;
 import org.olat.search.service.document.ProjectBrokerProjectDocument;
-import org.olat.search.service.indexer.FolderIndexer;
+import org.olat.search.service.indexer.AbstractHierarchicalIndexer;
 import org.olat.search.service.indexer.OlatFullIndexer;
 
 /**
  * Indexer for project-broker course-node.
  * @author Christian Guretzki
  */
-public class ProjectBrokerCourseNodeIndexer extends FolderIndexer implements CourseNodeIndexer {
-	private OLog log = Tracing.createLoggerFor(this.getClass()); 
+public class ProjectBrokerCourseNodeIndexer extends AbstractHierarchicalIndexer implements CourseNodeIndexer {
+	private static final OLog log = Tracing.createLoggerFor(ProjectBrokerCourseNodeIndexer.class); 
 
 	public static final String TYPE = "type.course.node.projectbroker";
 
 	private final static String SUPPORTED_TYPE_NAME = "org.olat.course.nodes.ProjectBrokerCourseNode";
 
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
-    SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-    courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setDocumentType(TYPE);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
+		courseNodeResourceContext.setBusinessControlFor(courseNode);
+		courseNodeResourceContext.setDocumentType(TYPE);
+		courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+		courseNodeResourceContext.setDescription(courseNode.getLongTitle());
        
-    // go further, index my projects
+		// go further, index my projects
 		CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager();
 		ProjectBrokerManager projectBrokerManager = CoreSpringFactory.getImpl(ProjectBrokerManager.class);
 		Long projectBrokerId = projectBrokerManager.getProjectBrokerId(cpm, courseNode);
@@ -71,12 +72,13 @@ public class ProjectBrokerCourseNodeIndexer extends FolderIndexer implements Cou
 				Project project = iterator.next();
 				Document document = ProjectBrokerProjectDocument.createDocument(courseNodeResourceContext, project);
 				indexWriter.addDocument(document);
-		  }
+			}
 		} else {
 			log.debug("projectBrokerId is null, courseNode=" + courseNode + " , course=" + course);
 		}
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/SPCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/SPCourseNodeIndexer.java
index 86c7d924d39..14d11759a13 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/SPCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/SPCourseNodeIndexer.java
@@ -45,14 +45,14 @@ import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.SPCourseNode;
 import org.olat.course.nodes.sp.SPEditController;
 import org.olat.search.service.SearchResourceContext;
-import org.olat.search.service.indexer.FolderIndexer;
+import org.olat.search.service.indexer.LeafIndexer;
 import org.olat.search.service.indexer.OlatFullIndexer;
 
 /**
  * Indexer for SP (SinglePage) course-node.
  * @author Christian Guretzki
  */
-public class SPCourseNodeIndexer extends FolderIndexer implements CourseNodeIndexer {
+public class SPCourseNodeIndexer extends LeafIndexer implements CourseNodeIndexer {
 	private static final OLog log = Tracing.createLoggerFor(SPCourseNodeIndexer.class);
 
 	// Must correspond with LocalString_xx.properties
@@ -60,40 +60,41 @@ public class SPCourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 	public final static String TYPE = "type.course.node.sp";
 
 	private final static String SUPPORTED_TYPE_NAME = "org.olat.course.nodes.SPCourseNode";
-  private final static boolean indexOnlyChosenFile = false;
+	private final static boolean indexOnlyChosenFile = false;
   
-  private static final Pattern HREF_PATTERN = Pattern.compile("href=\\\"([^\\\"]*)\\\"", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
-  private static final String HTML_SUFFIXES = "html htm xhtml xml";
+	private static final Pattern HREF_PATTERN = Pattern.compile("href=\\\"([^\\\"]*)\\\"", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+	private static final String HTML_SUFFIXES = "html htm xhtml xml";
 
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
 		if (log.isDebug()) log.debug("Index SinglePage...");
 
-    SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-    courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setDocumentType(TYPE);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
+		courseNodeResourceContext.setBusinessControlFor(courseNode);
+		courseNodeResourceContext.setDocumentType(TYPE);
+		courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+		courseNodeResourceContext.setDescription(courseNode.getLongTitle());
 
-    VFSContainer rootContainer = SPCourseNode.getNodeFolderContainer((SPCourseNode) courseNode, course.getCourseEnvironment());
-  	String chosenFile = (String) courseNode.getModuleConfiguration().get(SPEditController.CONFIG_KEY_FILE);
-  	// First: Index choosen HTML file
-  	if (log.isDebug()) log.debug("Index chosen file in SP. chosenFile=" + chosenFile);
- 	  VFSLeaf leaf = (VFSLeaf)rootContainer.resolve(chosenFile);
-  	if (leaf != null) {
-  	  String filePath = getPathFor(leaf);
-  	  if (log.isDebug()) log.debug("Found chosen file in SP. filePath=" + filePath );
-  	  doIndexVFSLeafByMySelf(courseNodeResourceContext, leaf, indexWriter, filePath);
-    	if (!indexOnlyChosenFile) {
-    		if (log.isDebug()) log.debug("Index sub pages in SP.");
-    		Set<String> alreadyIndexFileNames = new HashSet<String>();
-    		alreadyIndexFileNames.add(chosenFile);
-      	indexSubPages(courseNodeResourceContext,rootContainer,indexWriter,leaf,alreadyIndexFileNames,0,filePath);
-      } else {
-      	if (log.isDebug()) log.debug("Index only chosen file in SP.");
-      }
-  	} else {
-  		if (log.isDebug()) log.debug("Can not found choosen file in SP => Nothing indexed.");
-  	}
+		VFSContainer rootContainer = SPCourseNode.getNodeFolderContainer((SPCourseNode) courseNode, course.getCourseEnvironment());
+		String chosenFile = (String) courseNode.getModuleConfiguration().get(SPEditController.CONFIG_KEY_FILE);
+		// First: Index choosen HTML file
+		if (log.isDebug()) log.debug("Index chosen file in SP. chosenFile=" + chosenFile);
+		VFSLeaf leaf = (VFSLeaf)rootContainer.resolve(chosenFile);
+		if (leaf != null) {
+			String filePath = getPathFor(leaf);
+			if (log.isDebug()) log.debug("Found chosen file in SP. filePath=" + filePath );
+			doIndexVFSLeafByMySelf(courseNodeResourceContext, leaf, indexWriter, filePath);
+			if (!indexOnlyChosenFile) {
+				if (log.isDebug()) log.debug("Index sub pages in SP.");
+				Set<String> alreadyIndexFileNames = new HashSet<String>();
+				alreadyIndexFileNames.add(chosenFile);
+				indexSubPages(courseNodeResourceContext,rootContainer,indexWriter,leaf,alreadyIndexFileNames,0,filePath);
+			} else if (log.isDebug()) {
+				log.debug("Index only chosen file in SP.");
+			}
+		} else if (log.isDebug()) {
+			log.debug("Can not found choosen file in SP => Nothing indexed.");
+		}
 	}
 
 	public String getSupportedTypeName() {
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/STCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/STCourseNodeIndexer.java
index d7d4259dfe3..7a9ffc0a9e9 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/STCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/STCourseNodeIndexer.java
@@ -38,14 +38,14 @@ import org.olat.course.nodes.st.STCourseNodeEditController;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.search.service.SearchResourceContext;
 import org.olat.search.service.document.CourseNodeDocument;
-import org.olat.search.service.indexer.FolderIndexer;
+import org.olat.search.service.indexer.LeafIndexer;
 import org.olat.search.service.indexer.OlatFullIndexer;
 
 /**
  * Indexer for ST (Structure) course-node. 
  * @author Christian Guretzki
  */
-public class STCourseNodeIndexer extends FolderIndexer implements CourseNodeIndexer {
+public class STCourseNodeIndexer extends LeafIndexer implements CourseNodeIndexer {
 	private static final OLog log = Tracing.createLoggerFor(STCourseNodeIndexer.class);
 	
 	// Must correspond with LocalString_xx.properties
@@ -60,16 +60,17 @@ public class STCourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 		//
 	}
 
+	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter)
 	throws IOException,InterruptedException {
 		if (log.isDebug()) log.debug("Index StructureNode...");
 		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
 		courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setDocumentType(TYPE);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		courseNodeResourceContext.setDocumentType(TYPE);
+		courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+		courseNodeResourceContext.setDescription(courseNode.getLongTitle());
     
-	  Document document = CourseNodeDocument.createDocument(courseNodeResourceContext, courseNode);
+		Document document = CourseNodeDocument.createDocument(courseNodeResourceContext, courseNode);
 		indexWriter.addDocument(document);
 		
 		ModuleConfiguration config = courseNode.getModuleConfiguration();
@@ -83,6 +84,7 @@ public class STCourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 		}
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/TACourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/TACourseNodeIndexer.java
index 34b45677ab9..ae3ba59f83d 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/TACourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/TACourseNodeIndexer.java
@@ -58,37 +58,37 @@ public class TACourseNodeIndexer extends FolderIndexer implements CourseNodeInde
 
 	@Override
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) throws IOException,InterruptedException  {
-    SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
-    courseNodeResourceContext.setBusinessControlFor(courseNode);
-    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+		SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
+		courseNodeResourceContext.setBusinessControlFor(courseNode);
+		courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+		courseNodeResourceContext.setDescription(courseNode.getLongTitle());
     
-    // Index Task
-    File fTaskfolder = new File(FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFolderPathRelToFolderRoot(course.getCourseEnvironment(), courseNode));
-    VFSContainer taskRootContainer = new LocalFolderImpl(fTaskfolder);
-    courseNodeResourceContext.setDocumentType(TYPE_TASK);
-    doIndexVFSContainer(courseNodeResourceContext, taskRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
-    
-    // Index Dropbox
-    String dropboxFilePath = FolderConfig.getCanonicalRoot() + DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
-    File fDropboxFolder = new File(dropboxFilePath);
-    VFSContainer dropboxRootContainer = new LocalFolderImpl(fDropboxFolder);
-    courseNodeResourceContext.setDocumentType(TYPE_DROPBOX);
-    doIndexVFSContainer(courseNodeResourceContext, dropboxRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
-    
-    // Index Returnbox
-    String returnboxFilePath = FolderConfig.getCanonicalRoot() + ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
-    File fResturnboxFolder = new File(returnboxFilePath);
-    VFSContainer returnboxRootContainer = new LocalFolderImpl(fResturnboxFolder);
-    courseNodeResourceContext.setDocumentType(TYPE_RETURNBOX);
-    doIndexVFSContainer(courseNodeResourceContext, returnboxRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
-    
-    // Index Solutionbox
-    String solutionFilePath = FolderConfig.getCanonicalRoot() + SolutionController.getSolutionPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
-    File fSolutionFolder = new File(solutionFilePath);
-    VFSContainer solutionRootContainer = new LocalFolderImpl(fSolutionFolder);
-    courseNodeResourceContext.setDocumentType(TYPE_SOLUTIONBOX);
-    doIndexVFSContainer(courseNodeResourceContext, solutionRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+		// Index Task
+		File fTaskfolder = new File(FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFolderPathRelToFolderRoot(course.getCourseEnvironment(), courseNode));
+		VFSContainer taskRootContainer = new LocalFolderImpl(fTaskfolder);
+		courseNodeResourceContext.setDocumentType(TYPE_TASK);
+		doIndexVFSContainer(courseNodeResourceContext, taskRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+		
+		// Index Dropbox
+		String dropboxFilePath = FolderConfig.getCanonicalRoot() + DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
+		File fDropboxFolder = new File(dropboxFilePath);
+		VFSContainer dropboxRootContainer = new LocalFolderImpl(fDropboxFolder);
+		courseNodeResourceContext.setDocumentType(TYPE_DROPBOX);
+		doIndexVFSContainer(courseNodeResourceContext, dropboxRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+		
+		// Index Returnbox
+		String returnboxFilePath = FolderConfig.getCanonicalRoot() + ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
+		File fResturnboxFolder = new File(returnboxFilePath);
+		VFSContainer returnboxRootContainer = new LocalFolderImpl(fResturnboxFolder);
+		courseNodeResourceContext.setDocumentType(TYPE_RETURNBOX);
+		doIndexVFSContainer(courseNodeResourceContext, returnboxRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
+		
+		// Index Solutionbox
+		String solutionFilePath = FolderConfig.getCanonicalRoot() + SolutionController.getSolutionPathRelToFolderRoot(course.getCourseEnvironment(), courseNode);
+		File fSolutionFolder = new File(solutionFilePath);
+		VFSContainer solutionRootContainer = new LocalFolderImpl(fSolutionFolder);
+		courseNodeResourceContext.setDocumentType(TYPE_SOLUTIONBOX);
+		doIndexVFSContainer(courseNodeResourceContext, solutionRootContainer, indexWriter, "", FolderIndexerAccess.FULL_ACCESS);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/WikiCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/WikiCourseNodeIndexer.java
index 09ce829146e..b6360603385 100644
--- a/src/main/java/org/olat/search/service/indexer/repository/course/WikiCourseNodeIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/repository/course/WikiCourseNodeIndexer.java
@@ -38,14 +38,14 @@ import org.olat.modules.wiki.WikiPage;
 import org.olat.repository.RepositoryEntry;
 import org.olat.search.service.SearchResourceContext;
 import org.olat.search.service.document.WikiPageDocument;
-import org.olat.search.service.indexer.FolderIndexer;
+import org.olat.search.service.indexer.AbstractHierarchicalIndexer;
 import org.olat.search.service.indexer.OlatFullIndexer;
 
 /**
  * Indexer for Wiki course-node.
  * @author Christian Guretzki
  */
-public class WikiCourseNodeIndexer extends FolderIndexer implements CourseNodeIndexer {
+public class WikiCourseNodeIndexer extends AbstractHierarchicalIndexer implements CourseNodeIndexer {
 	private static final OLog log = Tracing.createLoggerFor(WikiCourseNodeIndexer.class);
 
 	// Must correspond with LocalString_xx.properties
@@ -58,20 +58,21 @@ public class WikiCourseNodeIndexer extends FolderIndexer implements CourseNodeIn
 	public void doIndex(SearchResourceContext repositoryResourceContext, ICourse course, CourseNode courseNode, OlatFullIndexer indexWriter) {
 		if (log.isDebug()) log.debug("Index wiki...");
 		String repoEntryName = "*name not available*";
-    try {
-  		RepositoryEntry repositoryEntry = courseNode.getReferencedRepositoryEntry();
-  		if(repositoryEntry == null) return;
-  		repoEntryName = repositoryEntry.getDisplayname();
+		try {
+			RepositoryEntry repositoryEntry = courseNode.getReferencedRepositoryEntry();
+			if(repositoryEntry == null) return;
+			
+			repoEntryName = repositoryEntry.getDisplayname();
 			Wiki wiki = WikiManager.getInstance().getOrLoadWiki(courseNode.getReferencedRepositoryEntry().getOlatResource());
 			// loop over all wiki pages
 			List<WikiPage> wikiPageList = wiki.getAllPagesWithContent();
 			for (WikiPage wikiPage : wikiPageList) {
-			  try {
+				try {
 					SearchResourceContext courseNodeResourceContext = new SearchResourceContext(repositoryResourceContext);
 					courseNodeResourceContext.setBusinessControlFor(courseNode);
 					courseNodeResourceContext.setDocumentType(TYPE);
-			    courseNodeResourceContext.setTitle(courseNode.getShortTitle());
-			    courseNodeResourceContext.setDescription(courseNode.getLongTitle());
+					courseNodeResourceContext.setTitle(courseNode.getShortTitle());
+					courseNodeResourceContext.setDescription(courseNode.getLongTitle());
 					courseNodeResourceContext.setFilePath(wikiPage.getPageName());
 
 					Document document = WikiPageDocument.createDocument(courseNodeResourceContext, wikiPage);
@@ -85,6 +86,7 @@ public class WikiCourseNodeIndexer extends FolderIndexer implements CourseNodeIn
 		}
 	}
 
+	@Override
 	public String getSupportedTypeName() {
 		return SUPPORTED_TYPE_NAME;
 	}
-- 
GitLab