001: /*
002: * regain - A file search engine providing plenty of formats
003: * Copyright (C) 2004 Til Schneider
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *
019: * Contact: Til Schneider, info@murfman.de
020: *
021: * CVS information:
022: * $RCSfile$
023: * $Source$
024: * $Date: 2006-08-21 11:37:35 +0200 (Mo, 21 Aug 2006) $
025: * $Author: til132 $
026: * $Revision: 232 $
027: */
028: package net.sf.regain.search;
029:
030: import java.io.File;
031: import java.io.IOException;
032: import java.util.HashMap;
033:
034: import net.sf.regain.RegainException;
035: import net.sf.regain.RegainToolkit;
036:
037: import org.apache.lucene.analysis.Analyzer;
038: import org.apache.lucene.index.IndexReader;
039: import org.apache.lucene.search.Hits;
040: import org.apache.lucene.search.IndexSearcher;
041: import org.apache.lucene.search.Query;
042:
043: /**
044: * Kapselt die Suche auf dem Suchindex.
045: * <p>
046: * Alle Suchanfragen werden synchronisiert. Außerdem wird im 10-Sekunden-Takt
047: * geprüft, ob ein neuer Index verfügbar ist. Wenn ja, dann wird der neue Index
048: * übernommen und der alte in einem Backup gesichert.
049: *
050: * @author Til Schneider, www.murfman.de
051: */
052: public class IndexSearcherManager {
053:
054: /**
055: * Die Zeit in Millisekunden in der der Update-Thread zwischen zwei
056: * Update-Prüfungen schlafen soll.
057: */
058: private static final int INDEX_UPDATE_THREAD_SLEEPTIME = 10000;
059:
060: /**
061: * Der Name des Index-Unterverzeichnisses, in das der neue Index gestellt
062: * wird, sobald er fertig ist.
063: * <p>
064: * Wenn dieses Verzeichnis existiert, dann wird die Suche auf den darin
065: * enthaltenen Index umgestellt.
066: */
067: private static final String NEW_INDEX_SUBDIR = "new";
068: /** Der Name des Index-Unterverzeichnisses, in dem der genutzte Index steht. */
069: private static final String WORKING_INDEX_SUBDIR = "index";
070: /**
071: * Der Name des Index-Unterverzeichnisses, in das der letzte Index gesichert
072: * werden soll.
073: */
074: private static final String BACKUP_INDEX_SUBDIR = "backup";
075:
076: /**
077: * Enthält für ein Index-Verzeichnis (key) den zuständigen
078: * IndexWriterManager (value).
079: */
080: private static HashMap mIndexManagerHash;
081:
082: /**
083: * Das Verzeichnis, in das der neue Index gestellt wird, sobald er fertig ist.
084: * <p>
085: * Wenn dieses Verzeichnis existiert, dann wird die Suche auf den darin
086: * enthaltenen Index umgestellt.
087: */
088: private File mNewIndexDir;
089: /** Das Verzeichnis, in dem der genutzte Index steht. */
090: private File mWorkingIndexDir;
091: /** Das Verzeichnis, in das der letzte Index gesichert werden soll. */
092: private File mBackupIndexDir;
093:
094: /**
095: * Der IndexSearcher auf dem die Suchen erfolgen.
096: * <p>
097: * Die Klasse IndexSearcher ist zwar thread-sicher, aber wir synchronisieren
098: * trotzdem alle Suchanfragen selbst, damit ein Austausch des Suchindex im
099: * laufenden Betrieb möglich ist.
100: */
101: private IndexSearcher mIndexSearcher;
102:
103: /** The IndexReader to use for reading information from an index. */
104: private IndexReader mIndexReader;
105:
106: /** Der Analyzer, der für Suchen verwendet werden soll. */
107: private Analyzer mAnalyzer;
108:
109: /** Der Thread, der alle 10 Sekunden prüft, ob ein neuer Suchindex vorhanden ist. */
110: private Thread mIndexUpdateThread;
111:
112: /**
113: * Holds for a field name (String) all distinct values the index has for that
114: * field (String[]).
115: */
116: private HashMap mFieldTermHash;
117:
118: /**
119: * Erzeugt eine neue IndexWriterManager-Instanz.
120: *
121: * @param indexDir Das Verzeichnis, in dem der Index steht.
122: */
123: private IndexSearcherManager(String indexDir) {
124: mNewIndexDir = new File(indexDir + File.separator
125: + NEW_INDEX_SUBDIR);
126: mWorkingIndexDir = new File(indexDir + File.separator
127: + WORKING_INDEX_SUBDIR);
128: mBackupIndexDir = new File(indexDir + File.separator
129: + BACKUP_INDEX_SUBDIR);
130:
131: mIndexUpdateThread = new Thread() {
132: public void run() {
133: indexUpdateThreadRun();
134: }
135: };
136: mIndexUpdateThread.setPriority(Thread.MIN_PRIORITY);
137: mIndexUpdateThread.start();
138: }
139:
140: /**
141: * Gibt den IndexWriterManager für das gegebene Index-Verzeichnis zurück.
142: *
143: * @param indexDir Das Verzeichnis, in dem der Index steht.
144: *
145: * @return Der IndexWriterManager für das Index-Verzeichnis.
146: */
147: public static synchronized IndexSearcherManager getInstance(
148: String indexDir) {
149: if (mIndexManagerHash == null) {
150: mIndexManagerHash = new HashMap();
151: }
152:
153: // Zuständigen IndexWriterManager aus der Hash zu holen
154: IndexSearcherManager manager = (IndexSearcherManager) mIndexManagerHash
155: .get(indexDir);
156: if (manager == null) {
157: // Für diesen Index gibt es noch keinen Manager -> einen anlegen
158: manager = new IndexSearcherManager(indexDir);
159: mIndexManagerHash.put(indexDir, manager);
160: }
161:
162: return manager;
163: }
164:
165: /**
166: * Sucht im Suchindex.
167: * <p>
168: * Hinweis: Suchen und Update-Checks laufen synchronisiert ab (also niemals
169: * gleichzeitig).
170: *
171: * @param query Die Suchanfrage.
172: *
173: * @return Die gefundenen Treffer.
174: * @throws RegainException Wenn die Suche fehl schlug.
175: */
176: public synchronized Hits search(Query query) throws RegainException {
177: if (mIndexSearcher == null) {
178: if (!mWorkingIndexDir.exists()) {
179: checkForIndexUpdate();
180: }
181:
182: try {
183: mIndexSearcher = new IndexSearcher(mWorkingIndexDir
184: .getAbsolutePath());
185: } catch (IOException exc) {
186: throw new RegainException(
187: "Creating index searcher failed", exc);
188: }
189: }
190:
191: try {
192: return mIndexSearcher.search(query);
193: } catch (IOException exc) {
194: throw new RegainException("Searching query failed", exc);
195: }
196: }
197:
198: /**
199: * Gets an IndexReader for the index.
200: * <p>
201: * NOTE: Must be called in a synchronized block.
202: *
203: * @return An IndexReader for the index.
204: * @throws RegainException If creating the IndexReader failed.
205: */
206: private IndexReader getIndexReader() throws RegainException {
207: if (mIndexReader == null) {
208: if (!mWorkingIndexDir.exists()) {
209: checkForIndexUpdate();
210: }
211:
212: try {
213: mIndexReader = IndexReader.open(mWorkingIndexDir
214: .getAbsolutePath());
215: } catch (IOException exc) {
216: throw new RegainException(
217: "Creating index reader failed", exc);
218: }
219: }
220:
221: return mIndexReader;
222: }
223:
224: /**
225: * Gets all distinct values a index has for a certain field. The values are
226: * sorted alphabetically.
227: *
228: * @param field The field to get the values for.
229: * @return All distinct values the index has for the field.
230: * @throws RegainException If reading the values failed.
231: */
232: public synchronized String[] getFieldValues(String field)
233: throws RegainException {
234: if (mFieldTermHash == null) {
235: mFieldTermHash = new HashMap();
236: }
237:
238: String[] valueArr = (String[]) mFieldTermHash.get(field);
239: if (valueArr == null) {
240: // Read the field values
241: HashMap valueMap = RegainToolkit.readFieldValues(
242: getIndexReader(), new String[] { field },
243: mWorkingIndexDir);
244: valueArr = (String[]) valueMap.get(field);
245:
246: // Copy the field values to our cache
247: mFieldTermHash.put(field, valueArr);
248: }
249:
250: return valueArr;
251: }
252:
253: /**
254: * Gets the total number of documents in the index.
255: *
256: * @return The total number of documents in the index.
257: * @throws RegainException If getting the document count failed.
258: */
259: public synchronized int getDocumentCount() throws RegainException {
260: return getIndexReader().numDocs();
261: }
262:
263: /**
264: * Gibt den Analyzer zurück, der für die Suche genutzt werden soll.
265: *
266: * @return Der Analyzer.
267: * @throws RegainException Wenn die Erzeugung des Analyzers fehl schlug.
268: */
269: public synchronized Analyzer getAnalyzer() throws RegainException {
270: if (mAnalyzer == null) {
271: if (!mWorkingIndexDir.exists()) {
272: // There is no working index -> check whether there is a new one
273: checkForIndexUpdate();
274: }
275:
276: if (!mWorkingIndexDir.exists()) {
277: // There is no working and no new index -> throw exception
278: throw new RegainException("No index found in "
279: + mWorkingIndexDir.getParentFile()
280: .getAbsolutePath());
281: }
282:
283: // Read the stopWordList and the exclusionList
284: File analyzerTypeFile = new File(mWorkingIndexDir,
285: "analyzerType.txt");
286: String analyzerType = RegainToolkit
287: .readStringFromFile(analyzerTypeFile);
288: File stopWordListFile = new File(mWorkingIndexDir,
289: "stopWordList.txt");
290: String[] stopWordList = RegainToolkit
291: .readListFromFile(stopWordListFile);
292: File exclusionListFile = new File(mWorkingIndexDir,
293: "exclusionList.txt");
294: String[] exclusionList = RegainToolkit
295: .readListFromFile(exclusionListFile);
296:
297: File untokenizedFieldNamesFile = new File(mWorkingIndexDir,
298: "untokenizedFieldNames.txt");
299: String[] untokenizedFieldNames;
300: if (untokenizedFieldNamesFile.exists()) {
301: untokenizedFieldNames = RegainToolkit
302: .readListFromFile(untokenizedFieldNamesFile);
303: } else {
304: untokenizedFieldNames = new String[0];
305: }
306:
307: // NOTE: Make shure to use the same analyzer in the crawler
308: mAnalyzer = RegainToolkit.createAnalyzer(analyzerType,
309: stopWordList, exclusionList, untokenizedFieldNames);
310: }
311:
312: return mAnalyzer;
313: }
314:
315: /**
316: * Die run()-Methode des Index-Update-Thread.
317: *
318: * @see #mIndexUpdateThread
319: */
320: void indexUpdateThreadRun() {
321: while (true) {
322: try {
323: checkForIndexUpdate();
324: } catch (RegainException exc) {
325: System.out.println("Updating index failed!");
326: exc.printStackTrace();
327: }
328:
329: try {
330: Thread.sleep(INDEX_UPDATE_THREAD_SLEEPTIME);
331: } catch (InterruptedException exc) {
332: }
333: }
334: }
335:
336: /**
337: * Prüft, ob ein neuer Index vorhanden ist. Wenn ja, dann wird die Suche auf den
338: * neuen Index umgestellt.
339: * <p>
340: * Hinweis: Suchen und Update-Checks laufen synchronisiert ab (also niemals
341: * gleichzeitig).
342: *
343: * @throws RegainException Falls die Umstellung auf den neuen Index fehl schlug.
344: */
345: private synchronized void checkForIndexUpdate()
346: throws RegainException {
347: if (mNewIndexDir.exists()) {
348: System.out.println("New index found on "
349: + new java.util.Date());
350:
351: // Close the IndexSearcher
352: if (mIndexSearcher != null) {
353: try {
354: mIndexSearcher.close();
355: } catch (IOException exc) {
356: throw new RegainException(
357: "Closing index searcher failed", exc);
358: }
359:
360: // Force the creation of a new IndexSearcher and Analyzer next time it
361: // will be needed
362: mIndexSearcher = null;
363: mAnalyzer = null;
364: }
365:
366: // Close the IndexReader
367: if (mIndexReader != null) {
368: try {
369: mIndexReader.close();
370: } catch (IOException exc) {
371: throw new RegainException(
372: "Closing index reader failed", exc);
373: }
374:
375: // Force the creation of a new IndexReader and mFieldTermHash next time
376: // it will be needed
377: mIndexReader = null;
378: mFieldTermHash = null;
379: }
380:
381: // Remove the old backup if it should still exist
382: if (mBackupIndexDir.exists()) {
383: RegainToolkit.deleteDirectory(mBackupIndexDir);
384: }
385:
386: // Backup the current index (if there is one)
387: if (mWorkingIndexDir.exists()) {
388: if (!mWorkingIndexDir.renameTo(mBackupIndexDir)) {
389: throw new RegainException("Renaming "
390: + mWorkingIndexDir + " to "
391: + mBackupIndexDir + " failed!");
392: }
393: }
394:
395: // Move the new index
396: if (!mNewIndexDir.renameTo(mWorkingIndexDir)) {
397: throw new RegainException("Renaming " + mNewIndexDir
398: + " to " + mWorkingIndexDir + " failed!");
399: }
400: }
401: }
402:
403: }
|