From d5e0275657d92829d72ba573d44c0267487792d9 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 23 Jul 2014 17:08:14 +0200
Subject: [PATCH] OO-990: refresh the index searcher if needed

---
 .../java/org/olat/core/util/CodeHelper.java   |   6 +
 .../org/olat/search/_spring/searchContext.xml |   4 +-
 .../olat/search/service/SearchCallable.java   |   4 +-
 .../search/service/SearchOrderByCallable.java |   6 +-
 .../search/service/SearchServiceImpl.java     | 249 ++++++++++++------
 .../olat/search/service/indexer/Index.java    |  19 +-
 .../service/indexer/IndexWriterHolder.java    |  17 +-
 .../search/service/indexer/IndexerEvent.java  |  43 +++
 .../search/service/indexer/JmsIndexer.java    |  29 +-
 .../service/indexer/OlatFullIndexer.java      |  39 +--
 10 files changed, 289 insertions(+), 127 deletions(-)
 create mode 100644 src/main/java/org/olat/search/service/indexer/IndexerEvent.java

diff --git a/src/main/java/org/olat/core/util/CodeHelper.java b/src/main/java/org/olat/core/util/CodeHelper.java
index d31ffa27559..e9f03803cdd 100644
--- a/src/main/java/org/olat/core/util/CodeHelper.java
+++ b/src/main/java/org/olat/core/util/CodeHelper.java
@@ -104,6 +104,12 @@ public class CodeHelper {
 		return UUID.randomUUID().toString();
 	}
 	
+	public static long nanoToMilliTime(long start) {
+		long end = System.nanoTime();
+		long takes = (end - start) / 1000000;
+		return takes;
+	}
+	
 	public static void printNanoTime(long start, String action) {
 		long end = System.nanoTime();
 		long takes = (end - start) / 1000000;
diff --git a/src/main/java/org/olat/search/_spring/searchContext.xml b/src/main/java/org/olat/search/_spring/searchContext.xml
index 22c7c3a1daa..d43f697c454 100644
--- a/src/main/java/org/olat/search/_spring/searchContext.xml
+++ b/src/main/java/org/olat/search/_spring/searchContext.xml
@@ -20,7 +20,8 @@
 		<constructor-arg index="0" ref="searchModule" />
 		<constructor-arg index="1" ref="mainIndexer" />
 		<constructor-arg index="2" ref="searchProvider" />
-		<constructor-arg index="3" ref="schedulerFactoryBean"/>
+		<constructor-arg index="3" ref="coordinatorManager"/>
+		<constructor-arg index="4" ref="schedulerFactoryBean"/>
 		<property name="lifeIndexer" ref="jmsIndexer"/>
 		<property name="metadataFields" ref="SearchMetadataFieldsProvider" />
 		<property name="searchExecutor" ref="searchExecutor" />
@@ -42,6 +43,7 @@
 	
 	<bean id="jmsIndexer" class="org.olat.search.service.indexer.JmsIndexer" init-method="springInit" destroy-method="stop">
 		<constructor-arg index="0" ref="searchModule" />
+		<constructor-arg index="1" ref="coordinatorManager"/>
 		<property name="connectionFactory" ref="indexConnectionFactory"/>
 		<property name="jmsQueue" ref="indexQueue"/>
 		<property name="searchService" ref="org.olat.search.service.${search.service}" />
diff --git a/src/main/java/org/olat/search/service/SearchCallable.java b/src/main/java/org/olat/search/service/SearchCallable.java
index 9071799bd25..a3bab180741 100644
--- a/src/main/java/org/olat/search/service/SearchCallable.java
+++ b/src/main/java/org/olat/search/service/SearchCallable.java
@@ -68,6 +68,7 @@ class SearchCallable implements Callable<SearchResults> {
 	
 	@Override
 	public SearchResults call() throws ParseException {
+		IndexSearcher searcher = null;
 		try {
 			boolean debug = log.isDebug();
 			
@@ -77,7 +78,7 @@ class SearchCallable implements Callable<SearchResults> {
 			}
 			
 			if(debug) log.debug("queryString=" + queryString);
-			IndexSearcher searcher = searchService.getIndexSearcher();
+			searcher = searchService.getIndexSearcher();
 			BooleanQuery query = searchService.createQuery(queryString, condQueries);
 			if(debug) log.debug("query=" + query);
 			
@@ -99,6 +100,7 @@ class SearchCallable implements Callable<SearchResults> {
 			log.error("", naex);
 			return null;
 		} finally {
+			searchService.releaseIndexSearcher(searcher);
 			DBFactory.getInstance().commitAndCloseSession();
 		}
 	}
diff --git a/src/main/java/org/olat/search/service/SearchOrderByCallable.java b/src/main/java/org/olat/search/service/SearchOrderByCallable.java
index 25657c30181..47b50713e5c 100644
--- a/src/main/java/org/olat/search/service/SearchOrderByCallable.java
+++ b/src/main/java/org/olat/search/service/SearchOrderByCallable.java
@@ -68,6 +68,7 @@ class SearchOrderByCallable implements Callable<List<Long>> {
 	
 	@Override
 	public List<Long> call() {
+		IndexSearcher searcher = null;
 		try {
 			if (!searchService.existIndex()) {
 				log.warn("Index does not exist, can't search for queryString: "+queryString);
@@ -75,11 +76,11 @@ class SearchOrderByCallable implements Callable<List<Long>> {
 			}
 			
 			log.info("queryString=" + queryString);
-			IndexSearcher searcher = searchService.getIndexSearcher();
+			searcher = searchService.getIndexSearcher();
 			BooleanQuery query = searchService.createQuery(queryString, condQueries);
 			//log.info("query=" + query);
 
-			int n = SearchServiceFactory.getService().getSearchModuleConfig().getMaxHits();
+			int n = searchService.getSearchModuleConfig().getMaxHits();
 			TopDocs docs;
 			if(orderBy != null && orderBy.length > 0 && orderBy[0] != null) {
 				SortField[] sortFields = new SortField[orderBy.length];
@@ -112,6 +113,7 @@ class SearchOrderByCallable implements Callable<List<Long>> {
 			log.error("", naex);
 			return null;
 		} finally {
+			searchService.releaseIndexSearcher(searcher);
 			DBFactory.getInstance().commitAndCloseSession();
 		}
 	}
diff --git a/src/main/java/org/olat/search/service/SearchServiceImpl.java b/src/main/java/org/olat/search/service/SearchServiceImpl.java
index 4058b85725b..c16e970ea07 100644
--- a/src/main/java/org/olat/search/service/SearchServiceImpl.java
+++ b/src/main/java/org/olat/search/service/SearchServiceImpl.java
@@ -35,10 +35,12 @@ import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiReader;
 import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
 import org.apache.lucene.queryparser.classic.ParseException;
@@ -47,10 +49,11 @@ import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ReferenceManager;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.FSDirectory;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.control.Event;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
 import org.olat.core.logging.AssertException;
@@ -58,6 +61,8 @@ import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.ArrayHelper;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.core.util.event.GenericEventListener;
 import org.olat.modules.qpool.model.QItemDocument;
 import org.olat.search.QueryException;
 import org.olat.search.SearchModule;
@@ -68,6 +73,7 @@ import org.olat.search.ServiceNotAvailableException;
 import org.olat.search.model.AbstractOlatDocument;
 import org.olat.search.service.indexer.FullIndexerStatus;
 import org.olat.search.service.indexer.Index;
+import org.olat.search.service.indexer.IndexerEvent;
 import org.olat.search.service.indexer.LifeFullIndexer;
 import org.olat.search.service.indexer.MainIndexer;
 import org.olat.search.service.searcher.JmsSearchProvider;
@@ -80,18 +86,16 @@ import org.quartz.SchedulerException;
  * 
  * @author Christian Guretzki
  */
-public class SearchServiceImpl implements SearchService {
+public class SearchServiceImpl implements SearchService, GenericEventListener {
 	private static final OLog log = Tracing.createLoggerFor(SearchServiceImpl.class);
 	
 	private Index indexer;	
 	private SearchModule searchModuleConfig;
 	private MainIndexer mainIndexer;
 	private Scheduler scheduler;
+	private CoordinatorManager coordinatorManager;
 
 	private Analyzer analyzer;
-	private IndexSearcher searcher;
-	private DirectoryReader reader;
-	private DirectoryReader permReader;
 	
 	private LifeFullIndexer lifeIndexer;
 	private SearchSpellChecker searchSpellChecker;
@@ -100,9 +104,9 @@ public class SearchServiceImpl implements SearchService {
 	
 	/** Counts number of search queries since last restart. */
 	private long queryCount = 0;
-	private Object createIndexSearcherLock = new Object();
 	
 	private ExecutorService searchExecutor;
+	private OOSearcherManager indexSearcherRefMgr;
 
 	private String fields[] = {
 			AbstractOlatDocument.TITLE_FIELD_NAME, AbstractOlatDocument.DESCRIPTION_FIELD_NAME,
@@ -122,14 +126,17 @@ public class SearchServiceImpl implements SearchService {
 	/**
 	 * [used by spring]
 	 */
-	private SearchServiceImpl(SearchModule searchModule, MainIndexer mainIndexer, JmsSearchProvider searchProvider,
+	private SearchServiceImpl(SearchModule searchModule, MainIndexer mainIndexer,
+			JmsSearchProvider searchProvider, CoordinatorManager coordinatorManager,
 			Scheduler scheduler) {
 		log.info("Start SearchServiceImpl constructor...");
 		this.scheduler = scheduler;
 		this.searchModuleConfig = searchModule;
 		this.mainIndexer = mainIndexer;
+		this.coordinatorManager = coordinatorManager;
 		analyzer = new StandardAnalyzer(SearchService.OO_LUCENE_VERSION);
 		searchProvider.setSearchService(this);
+		coordinatorManager.getCoordinator().getEventBus().registerFor(this, null, IndexerEvent.INDEX_ORES);
 	}
 	
 	public void setSearchExecutor(ExecutorService searchExecutor) {
@@ -155,11 +162,11 @@ public class SearchServiceImpl implements SearchService {
 	/**
 	 * Start the job indexer
 	 */
+	@Override
 	public void startIndexing() {
 		if (indexer==null) throw new AssertException ("Try to call startIndexing() but indexer is null");
 		
 		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...");
@@ -171,11 +178,11 @@ public class SearchServiceImpl implements SearchService {
 	/**
 	 * Interrupt the job indexer
 	 */
+	@Override
 	public void stopIndexing() {
 		if (indexer==null) throw new AssertException ("Try to call stopIndexing() but indexer is null");
 
 		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.");
@@ -188,9 +195,9 @@ public class SearchServiceImpl implements SearchService {
 		return indexer;
 	}
 
+	@Override
 	public void init() {
 		log.info("init searchModuleConfig=" + searchModuleConfig);
-
 		log.info("Running with indexPath=" + searchModuleConfig.getFullIndexPath());
 		log.info("        tempIndexPath=" + searchModuleConfig.getFullTempIndexPath());
 		log.info("        generateAtStartup=" + searchModuleConfig.getGenerateAtStartup());
@@ -202,20 +209,45 @@ public class SearchServiceImpl implements SearchService {
 		searchSpellChecker.setSpellCheckEnabled(searchModuleConfig.getSpellCheckEnabled());
 		searchSpellChecker.setSearchExecutor(searchExecutor);
 		
-	  indexer = new Index(searchModuleConfig, searchSpellChecker, mainIndexer, lifeIndexer);
+		indexer = new Index(searchModuleConfig, searchSpellChecker, mainIndexer, lifeIndexer, coordinatorManager);
 
-	  indexPath = searchModuleConfig.getFullIndexPath();	
-	  permanentIndexPath = searchModuleConfig.getFullPermanentIndexPath();
+		indexPath = searchModuleConfig.getFullIndexPath();	
+		permanentIndexPath = searchModuleConfig.getFullPermanentIndexPath();
+		
+		createIndexSearcherManager();
 
-  	if (startingFullIndexingAllowed()) {
-  		try {
+		if (startingFullIndexingAllowed()) {
+			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");
+		}
+		log.info("init DONE");
+	}
+	
+	private void createIndexSearcherManager() {
+		try {
+			if(indexSearcherRefMgr == null) {
+				if(existIndex()) {
+					indexSearcherRefMgr = new OOSearcherManager(this);
+				}
+			} else {
+				indexSearcherRefMgr.needRefresh();
+			}
+		} catch (IOException e) {
+			log.error("Cannot initialized the searcher manager", e);
+		}
+	}
+
+	@Override
+	public void event(Event event) {
+		if(event instanceof IndexerEvent) {
+			if(IndexerEvent.INDEX_CREATED.equals(event.getCommand())) {
+				createIndexSearcherManager();
+			}
+		}
 	}
 
 	/**
@@ -277,16 +309,16 @@ public class SearchServiceImpl implements SearchService {
 			String[] fieldsArr = getFieldsToSearchIn();
 			QueryParser queryParser = new MultiFieldQueryParser(SearchService.OO_LUCENE_VERSION, fieldsArr, analyzer);
 			queryParser.setLowercaseExpandedTerms(false);//some add. fields are not tokenized and not lowered case
-	  	Query multiFieldQuery = queryParser.parse(queryString.toLowerCase());
-	  	query.add(multiFieldQuery, Occur.MUST);
+			Query multiFieldQuery = queryParser.parse(queryString.toLowerCase());
+			query.add(multiFieldQuery, Occur.MUST);
 		}
 		
 		if(condQueries != null && !condQueries.isEmpty()) {
 			for(String condQueryString:condQueries) {
 				QueryParser condQueryParser = new QueryParser(SearchService.OO_LUCENE_VERSION, condQueryString, analyzer);
 				condQueryParser.setLowercaseExpandedTerms(false);
-		  	Query condQuery = condQueryParser.parse(condQueryString);
-		  	query.add(condQuery, Occur.MUST);
+				Query condQuery = condQueryParser.parse(condQueryString);
+				query.add(condQuery, Occur.MUST);
 			}
 		}
 		return query;
@@ -300,24 +332,29 @@ public class SearchServiceImpl implements SearchService {
 	 * Delegates impl to the searchSpellChecker.
 	 * @see org.olat.search.service.searcher.OLATSearcher#spellCheck(java.lang.String)
 	 */
+	@Override
 	public Set<String> spellCheck(String query) {
 		if(searchSpellChecker==null) throw new AssertException ("Try to call spellCheck() in Search.java but searchSpellChecker is null");
 		return searchSpellChecker.check(query);
 	}
 
+	@Override
 	public long getQueryCount() {
 		return queryCount;
 	}
-	
+
+	@Override
 	public SearchServiceStatus getStatus() {
 		return new SearchServiceStatusImpl(indexer,this);
 	}
 
+	@Override
 	public void setIndexInterval(long indexInterval) {
 		if (indexer==null) throw new AssertException ("Try to call setIndexInterval() but indexer is null");
 		indexer.setIndexInterval(indexInterval);
 	}
-	
+
+	@Override
 	public long getIndexInterval() {
 		if (indexer==null) throw new AssertException ("Try to call setIndexInterval() but indexer is null");
 		return indexer.getIndexInterval();
@@ -327,10 +364,12 @@ public class SearchServiceImpl implements SearchService {
 	 * 
 	 * @return  Resturn search module configuration.
 	 */
+	@Override
 	public SearchModule getSearchModuleConfig() {
 		return searchModuleConfig;
 	}
 
+	@Override
 	public void stop() {
 		SearchServiceStatus status = getStatus();
 		String statusStr = status.getStatus();
@@ -338,66 +377,113 @@ public class SearchServiceImpl implements SearchService {
 			stopIndexing();
 		}
 		try {
-			if (searcher != null) {
-				searcher.getIndexReader().close();
-				searcher = null;
+			if (indexSearcherRefMgr != null) {
+				indexSearcherRefMgr.close();
+				indexSearcherRefMgr = null;
 			}
 		} catch (Exception e) {
 			log.error("", e);
 		}
 	}
 
+	@Override
 	public boolean isEnabled() {
 		return true;
 	}
 	
-	protected IndexSearcher getIndexSearcher() throws ServiceNotAvailableException, IOException {
-		if(searcher == null) {
-			synchronized (createIndexSearcherLock) {//o_clusterOK by:fj if service is only configured on one vm, which is recommended way
-				if (searcher == null) {
-					try {
-						getIndexSearcher(indexPath, permanentIndexPath);
-					} catch(IOException ioEx) {
-						log.warn("Can not create searcher", ioEx);
-						throw new ServiceNotAvailableException("Index is not available");
-					}
-				}
-			}
+	protected IndexSearcher getIndexSearcher()
+	throws ServiceNotAvailableException, IOException {
+		if(indexSearcherRefMgr == null) {
+			throw new ServiceNotAvailableException("Local search not available");
 		}
+		
+		indexSearcherRefMgr.maybeRefresh();
+		return indexSearcherRefMgr.acquire();
+	}
 	
-		return getIndexSearcher(indexPath, permanentIndexPath);
+	protected void releaseIndexSearcher(IndexSearcher s) {
+		try {
+			indexSearcherRefMgr.release(s);
+		} catch (IOException e) {
+			log.error("Error while releasing index searcher", e);
+		}
 	}
+	
 
-	private synchronized IndexSearcher getIndexSearcher(String path, String permanentPath)
-	throws IOException {
-		boolean hasChanged = false;
-		if(reader == null) {
-			hasChanged = true;
-			reader = DirectoryReader.open(FSDirectory.open(new File(path)));
-		} else {
-			DirectoryReader newReader = DirectoryReader.openIfChanged(reader);
-			if(newReader != null) {
-				hasChanged = true;
-				reader = newReader;
-			}
+	private IndexSearcher newSearcher() throws IOException {
+		DirectoryReader classicReader = DirectoryReader.open(FSDirectory.open(new File(indexPath)));
+		DirectoryReader permanentReader = DirectoryReader.open(FSDirectory.open(new File(permanentIndexPath)));
+		OOMultiReader mReader = new OOMultiReader(classicReader, permanentReader);
+		return new IndexSearcher(mReader);
+	}
+
+	private static class OOMultiReader extends MultiReader {
+		
+		private final DirectoryReader reader;
+		private final DirectoryReader permanentReader;
+		
+		public OOMultiReader(DirectoryReader reader, DirectoryReader permanentReader) {
+			super(reader, permanentReader);
+			this.reader = reader;
+			this.permanentReader = permanentReader;
+		}
+
+		public DirectoryReader getReader() {
+			return reader;
+		}
+
+		public DirectoryReader getPermanentReader() {
+			return permanentReader;
+		}
+	}
+
+	private static class OOSearcherManager extends ReferenceManager<IndexSearcher> {
+		
+		private final SearchServiceImpl factory;
+		private AtomicBoolean refresh = new AtomicBoolean(false);
+		
+		public OOSearcherManager(SearchServiceImpl factory) throws IOException {
+			this.factory = factory;
+			this.current = getSearcher(factory);
 		}
 		
-		if(permReader == null) {
-			hasChanged = true;
-			permReader = DirectoryReader.open(FSDirectory.open(new File(permanentPath)));
-		} else {
-			DirectoryReader newReader = DirectoryReader.openIfChanged(permReader);
-			if(newReader != null) {
-				hasChanged = true;
-				permReader = newReader;
-			}
+		protected void needRefresh() {
+			refresh.getAndSet(true);
+		}
+
+		@Override
+		protected void decRef(IndexSearcher reference) throws IOException {
+			reference.getIndexReader().decRef();
+		}
+
+		@Override
+		protected IndexSearcher refreshIfNeeded(IndexSearcher referenceToRefresh)
+		throws IOException {
+		    final OOMultiReader r = (OOMultiReader)referenceToRefresh.getIndexReader();
+		    final IndexReader newReader = DirectoryReader.openIfChanged(r.getReader());
+		    final IndexReader newPermReader = DirectoryReader.openIfChanged(r.getPermanentReader());
+		    if (newReader == null && newPermReader == null) {
+		    	return null;
+		    } else {
+		    	return getSearcher(factory);
+		    }
+		}
+
+		@Override
+		protected boolean tryIncRef(IndexSearcher reference) throws IOException {
+			return reference.getIndexReader().tryIncRef();
+		}
+
+		@Override
+		protected int getRefCount(IndexSearcher reference) {
+			return reference.getIndexReader().getRefCount();
 		}
 		
-		if(hasChanged) {
-			MultiReader mReader = new MultiReader(reader, permReader);
-			searcher = new IndexSearcher(mReader);
+		public static IndexSearcher getSearcher(SearchServiceImpl searcherFactory)
+		throws IOException {
+			IndexSearcher searcher = searcherFactory.newSearcher();
+			return searcher;
 		}
-		return searcher;
 	}
 
 	/**
@@ -442,23 +528,22 @@ public class SearchServiceImpl implements SearchService {
 	 * @return  TRUE: Starting is allowed
 	 */
 	private boolean startingFullIndexingAllowed() {
-  	if (searchModuleConfig.getGenerateAtStartup()) {
-  		Calendar calendar = Calendar.getInstance();
-  		calendar.setTime(new Date());
-  		// check day, Restart only at config day of week, 0-7 8=every day 
-  		int dayNow = calendar.get(Calendar.DAY_OF_WEEK);
-  		int restartDayOfWeek = searchModuleConfig.getRestartDayOfWeek();
-  		if (restartDayOfWeek == 0 || (dayNow == restartDayOfWeek) ) {
-    		// check time, Restart only in the config time-slot e.g. 01:00 - 03:00
-    		int hourNow = calendar.get(Calendar.HOUR_OF_DAY);
-    		int restartWindowStart = searchModuleConfig.getRestartWindowStart();
-    		int restartWindowEnd   = searchModuleConfig.getRestartWindowEnd();
-    		if ( (restartWindowStart <= hourNow) && (hourNow < restartWindowEnd) ) {
-    			return true;
-    		}  			
-  		}
-  	}
-  	return false;
+		if (searchModuleConfig.getGenerateAtStartup()) {
+			Calendar calendar = Calendar.getInstance();
+			calendar.setTime(new Date());
+			// check day, Restart only at config day of week, 0-7 8=every day 
+			int dayNow = calendar.get(Calendar.DAY_OF_WEEK);
+			int restartDayOfWeek = searchModuleConfig.getRestartDayOfWeek();
+			if (restartDayOfWeek == 0 || (dayNow == restartDayOfWeek) ) {
+				// check time, Restart only in the config time-slot e.g. 01:00 - 03:00
+				int hourNow = calendar.get(Calendar.HOUR_OF_DAY);
+				int restartWindowStart = searchModuleConfig.getRestartWindowStart();
+				int restartWindowEnd   = searchModuleConfig.getRestartWindowEnd();
+				if ( (restartWindowStart <= hourNow) && (hourNow < restartWindowEnd) ) {
+					return true;
+				}  			
+			}
+		}
+		return false;
 	}
-
 }
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 956c9df8555..87668bad24d 100644
--- a/src/main/java/org/olat/search/service/indexer/Index.java
+++ b/src/main/java/org/olat/search/service/indexer/Index.java
@@ -35,6 +35,7 @@ import org.olat.core.helpers.Settings;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
+import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.search.SearchModule;
 import org.olat.search.service.spell.SearchSpellChecker;
 
@@ -62,14 +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, LifeFullIndexer lifeIndexer) {
+	public Index(SearchModule searchModuleConfig, SearchSpellChecker spellChecker, MainIndexer mainIndexer,
+			LifeFullIndexer lifeIndexer, CoordinatorManager coordinatorManager) {
 		this.spellChecker = spellChecker;
 		this.indexPath = searchModuleConfig.getFullIndexPath();
 		this.tempIndexPath = searchModuleConfig.getFullTempIndexPath();
 		this.permanentIndexPath = searchModuleConfig.getFullPermanentIndexPath();
 		this.lifeIndexer = lifeIndexer;
 		
-		fullIndexer = new OlatFullIndexer(this, searchModuleConfig, mainIndexer);
+		fullIndexer = new OlatFullIndexer(this, searchModuleConfig, mainIndexer, coordinatorManager);
 	}
 
 	/**
@@ -78,9 +80,9 @@ public class Index {
 	public void startFullIndex() {
 		// do not start search engine in test mode, some repository tests might lead to nullpointers
 		// since only dummy entries are generated (or fix the search service to handle those correctly)
-		if ( ! Settings.isJUnitTest()) {
+		if (!Settings.isJUnitTest()) {
 			lifeIndexer.fullIndex();
-		  fullIndexer.startIndexing();
+			fullIndexer.startIndexing();
 		}
 	}
 	
@@ -123,12 +125,7 @@ public class Index {
 	 */
 	public void indexingIsDone() {
 		// Full indexing is done => move tempIndex to index dir
-		moveTempIndexToIndex(tempIndexPath,indexPath);
-		spellChecker.createSpellIndex();
-	}
-
-	private void moveTempIndexToIndex(String tempIndexPath, String indexPath) {
-		File indexDir = new File( indexPath );
+		File indexDir = new File(indexPath);
 		if (!indexDir.exists()) {
 		  indexDir.mkdirs();
 		}
@@ -138,6 +135,8 @@ public class Index {
 		FileUtils.deleteDirsAndFiles(indexDir, true, false);
 		FileUtils.copyDirContentsToDir(new File(tempIndexDir, "main") , indexDir ,true, "search indexer move tmp index");
 		log.info("New generated Index ready to use." );
+		
+		spellChecker.createSpellIndex();
 	}
 	
 	public OlatFullIndexer getIndexer() {
diff --git a/src/main/java/org/olat/search/service/indexer/IndexWriterHolder.java b/src/main/java/org/olat/search/service/indexer/IndexWriterHolder.java
index 34e67551d33..0f11373727e 100644
--- a/src/main/java/org/olat/search/service/indexer/IndexWriterHolder.java
+++ b/src/main/java/org/olat/search/service/indexer/IndexWriterHolder.java
@@ -27,6 +27,7 @@ import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.store.Directory;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.CodeHelper;
 
 /**
  * 
@@ -49,27 +50,33 @@ public class IndexWriterHolder {
 		this.indexer = indexer;
 	}
 	
-	public synchronized void ensureIndexExists() {
+	/**
+	 * @return true if it has created the index
+	 */
+	public synchronized boolean ensureIndexExists() {
+		boolean created = false;
 		IndexWriter writer = null;
 		try {
 			if(!DirectoryReader.indexExists(indexPath)) {
 				writer = getAndLock();
+				created = true;
 			}
 		} catch (IOException e) {
 			log.error("",  e);
 		} finally {
 			release(writer);
 		}
+		return created;
 	}
 
 	public synchronized IndexWriter getAndLock() throws IOException {
 		if(writerRef == null) {
-			long start = System.currentTimeMillis();
+			long start = System.nanoTime();
 			IndexWriter indexWriter = new IndexWriter(indexPath, indexer.newIndexWriterConfig());
 			if(!DirectoryReader.indexExists(indexPath)) {
 				indexWriter.commit();//make sure it exists
 			}
-			log.info("Opening writer takes (ms): " + (System.currentTimeMillis() - start));
+			log.info("Opening writer takes (ms): " + CodeHelper.nanoToMilliTime(start));
 			writerRef = indexWriter;
 		}
 		counter.incrementAndGet();
@@ -81,11 +88,11 @@ public class IndexWriterHolder {
 			try {
 				int used = counter.decrementAndGet();
 				if(used == 0) {
-					long start = System.currentTimeMillis();
+					long start = System.nanoTime();
 					indexWriter.commit();
 					indexWriter.close();
 					writerRef = null;
-					log.info("Close writer takes (ms): " + (System.currentTimeMillis() - start));
+					log.info("Close writer takes (ms): " + CodeHelper.nanoToMilliTime(start));
 				}
 			} catch (Exception e) {
 				log.error("", e);
diff --git a/src/main/java/org/olat/search/service/indexer/IndexerEvent.java b/src/main/java/org/olat/search/service/indexer/IndexerEvent.java
new file mode 100644
index 00000000000..f14fbd4bd8c
--- /dev/null
+++ b/src/main/java/org/olat/search/service/indexer/IndexerEvent.java
@@ -0,0 +1,43 @@
+/**
+ * <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.search.service.indexer;
+
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.util.event.MultiUserEvent;
+import org.olat.core.util.resource.OresHelper;
+
+/**
+ * 
+ * Initial date: 23.07.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class IndexerEvent extends MultiUserEvent {
+	
+	public static final String INDEX_CREATED = "index-created";
+	public static final OLATResourceable INDEX_ORES = OresHelper.createOLATResourceableType("Indexer");
+
+	private static final long serialVersionUID = -2979077265102230245L;
+
+	public IndexerEvent(String cmd) {
+		super(cmd);
+	}
+
+}
diff --git a/src/main/java/org/olat/search/service/indexer/JmsIndexer.java b/src/main/java/org/olat/search/service/indexer/JmsIndexer.java
index fc384be96d1..fbfbc83da9d 100644
--- a/src/main/java/org/olat/search/service/indexer/JmsIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/JmsIndexer.java
@@ -54,6 +54,7 @@ import org.apache.lucene.store.FSDirectory;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.search.SearchModule;
 import org.olat.search.SearchService;
 import org.olat.search.model.AbstractOlatDocument;
@@ -78,6 +79,7 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 	private QueueConnection connection;
 	
 	private SearchService searchService;
+	private CoordinatorManager coordinatorManager;
 
 	private String permanentIndexPath;
 	private DirectoryReader reader;
@@ -88,10 +90,11 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 	
 	private List<LifeIndexer> indexers = new ArrayList<LifeIndexer>();
 	
-	public JmsIndexer(SearchModule searchModuleConfig) {
+	public JmsIndexer(SearchModule searchModuleConfig, CoordinatorManager coordinatorManager) {
 		indexingNode = searchModuleConfig.isSearchServiceEnabled();
 		ramBufferSizeMB = searchModuleConfig.getRAMBufferSizeMB();
 		permanentIndexPath = searchModuleConfig.getFullPermanentIndexPath();
+		this.coordinatorManager = coordinatorManager;
 	}
 
 	public Queue getJmsQueue() {
@@ -172,8 +175,12 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 			File tempIndexDir = new File(permanentIndexPath);
 			Directory indexPath = FSDirectory.open(tempIndexDir);
 			if(indexingNode) {
-				permanentIndexWriter = new IndexWriterHolder (indexPath, this);
-				permanentIndexWriter.ensureIndexExists();
+				permanentIndexWriter = new IndexWriterHolder(indexPath, this);
+				boolean created = permanentIndexWriter.ensureIndexExists();
+				if(created) {
+					IndexerEvent event = new IndexerEvent(IndexerEvent.INDEX_CREATED);
+					coordinatorManager.getCoordinator().getEventBus().fireEventToListenersOf(event, IndexerEvent.INDEX_ORES);
+				}
 			}
 			reader = DirectoryReader.open(indexPath);
 		} catch (IOException e) {
@@ -297,8 +304,8 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 	private void doIndex(JmsIndexWork workUnit) {
 		if(searchService instanceof SearchServiceImpl) {
 			String type = workUnit.getIndexType();
-			List<LifeIndexer> indexers = getIndexerByType(type);
-			for(LifeIndexer indexer:indexers) {
+			List<LifeIndexer> lifeIndexers = getIndexerByType(type);
+			for(LifeIndexer indexer:lifeIndexers) {
 				indexer.indexDocument(workUnit.getKey(), this);
 			}
 		}
@@ -307,8 +314,8 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 	private void doDelete(JmsIndexWork workUnit) {
 		if(searchService instanceof SearchServiceImpl) {
 			String type = workUnit.getIndexType();
-			List<LifeIndexer> indexers = getIndexerByType(type);
-			for(LifeIndexer indexer:indexers) {
+			List<LifeIndexer> lifeIndexers = getIndexerByType(type);
+			for(LifeIndexer indexer:lifeIndexers) {
 				indexer.deleteDocument(workUnit.getKey(), this);
 			}
 		}
@@ -360,8 +367,8 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 			String resourceUrl = document.get(AbstractOlatDocument.RESOURCEURL_FIELD_NAME);
 			Term uuidTerm = new Term(AbstractOlatDocument.RESOURCEURL_FIELD_NAME, resourceUrl);
 
-			DirectoryReader reader = getReader();
-			IndexSearcher searcher = new IndexSearcher(reader);
+			DirectoryReader currentReader = getReader();
+			IndexSearcher searcher = new IndexSearcher(currentReader);
 			TopDocs hits = searcher.search(new TermQuery(uuidTerm), 10);
 			writer = permanentIndexWriter.getAndLock();
 			if(hits.totalHits > 0) {
@@ -381,8 +388,8 @@ public class JmsIndexer implements MessageListener, LifeFullIndexer {
 		try {
 			String resourceUrl = document.get(AbstractOlatDocument.RESOURCEURL_FIELD_NAME);
 			Term uuidTerm = new Term(AbstractOlatDocument.RESOURCEURL_FIELD_NAME, resourceUrl);
-			DirectoryReader reader = getReader();
-			IndexSearcher searcher = new IndexSearcher(reader);
+			DirectoryReader currentReader = getReader();
+			IndexSearcher searcher = new IndexSearcher(currentReader);
 			TopDocs hits = searcher.search(new TermQuery(uuidTerm), 10);
 			if(hits.totalHits > 0) {
 				writer.updateDocument(uuidTerm, document);
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 c077e551438..418453b5dd0 100644
--- a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
+++ b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java
@@ -47,6 +47,7 @@ import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.WorkThreadInformations;
+import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.search.SearchModule;
 import org.olat.search.SearchService;
 import org.olat.search.model.OlatDocument;
@@ -71,12 +72,13 @@ public class OlatFullIndexer {
 	 */
 	private Index index;
 	private IndexWriter indexWriter;
-	
-	
+
 	/** Flag to stop indexing. */
 	private boolean stopIndexing;
   /** When restartIndexingWhenFinished is true, the restart interval in ms can be set. */
 	private long indexInterval = 500;
+
+	private double ramBufferSizeMB;
 	
 	/** Current status of full-indexer. */
 	private FullIndexerStatus fullIndexerStatus;
@@ -99,7 +101,8 @@ public class OlatFullIndexer {
 	private Map<String,Integer> fileTypeCounters;
 
 	private MainIndexer mainIndexer;
-	private double ramBufferSizeMB;
+	private CoordinatorManager coordinatorManager;
+
 	
 	/**
 	 * 
@@ -108,18 +111,20 @@ public class OlatFullIndexer {
 	 * @param restartInterval Restart interval in milliseconds.
 	 * @param indexInterval   Sleep time in milliseconds between adding documents.
 	 */
-	public OlatFullIndexer(Index index, SearchModule searchModuleConfig, MainIndexer mainIndexer) {
-    this.index = index;
-    this.mainIndexer = mainIndexer;
-    tempIndexPath = searchModuleConfig.getFullTempIndexPath();
-    indexInterval = searchModuleConfig.getIndexInterval();
-    numberIndexWriter = searchModuleConfig.getNumberIndexWriter();
-    documentsPerInterval = searchModuleConfig.getDocumentsPerInterval();
-    ramBufferSizeMB = searchModuleConfig.getRAMBufferSizeMB();
-    fullIndexerStatus = new FullIndexerStatus(numberIndexWriter);    
-    stopIndexing = true;
-    documentQueue = new Vector<Document>();
-    resetDocumentCounters();
+	public OlatFullIndexer(Index index, SearchModule searchModuleConfig,
+			MainIndexer mainIndexer, CoordinatorManager coordinatorManager) {
+		this.index = index;
+		this.mainIndexer = mainIndexer;
+		this.coordinatorManager = coordinatorManager;
+		tempIndexPath = searchModuleConfig.getFullTempIndexPath();
+		indexInterval = searchModuleConfig.getIndexInterval();
+		numberIndexWriter = searchModuleConfig.getNumberIndexWriter();
+		documentsPerInterval = searchModuleConfig.getDocumentsPerInterval();
+		ramBufferSizeMB = searchModuleConfig.getRAMBufferSizeMB();
+		fullIndexerStatus = new FullIndexerStatus(numberIndexWriter);    
+		stopIndexing = true;
+		documentQueue = new Vector<Document>();
+		resetDocumentCounters();
 	}
 	
 	/**
@@ -228,6 +233,10 @@ public class OlatFullIndexer {
 			indexWriter.close();
 			indexWriter = null;
 			indexWriterWorkers = null;
+			
+			//created because the index is deleted and copied
+			IndexerEvent event = new IndexerEvent(IndexerEvent.INDEX_CREATED);
+			coordinatorManager.getCoordinator().getEventBus().fireEventToListenersOf(event, IndexerEvent.INDEX_ORES);
 		} catch (IOException e) {
 			log.warn("Can not create IndexWriter, indexname=" + tempIndexPath, e);
 		} finally {
-- 
GitLab