KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > help > internal > search > SearchIndex


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.help.internal.search;
12
13 import java.io.File JavaDoc;
14 import java.io.FileOutputStream JavaDoc;
15 import java.io.IOException JavaDoc;
16 import java.io.InputStream JavaDoc;
17 import java.io.RandomAccessFile JavaDoc;
18 import java.net.MalformedURLException JavaDoc;
19 import java.net.URL JavaDoc;
20 import java.nio.channels.FileLock JavaDoc;
21 import java.nio.channels.OverlappingFileLockException JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Locale JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import java.util.zip.ZipEntry JavaDoc;
32 import java.util.zip.ZipInputStream JavaDoc;
33
34 import org.apache.lucene.document.Document;
35 import org.apache.lucene.document.Field;
36 import org.apache.lucene.index.IndexReader;
37 import org.apache.lucene.index.IndexWriter;
38 import org.apache.lucene.index.Term;
39 import org.apache.lucene.index.TermDocs;
40 import org.apache.lucene.search.BooleanQuery;
41 import org.apache.lucene.search.Hits;
42 import org.apache.lucene.search.IndexSearcher;
43 import org.apache.lucene.search.Query;
44 import org.apache.lucene.store.Directory;
45 import org.apache.lucene.store.FSDirectory;
46 import org.eclipse.core.runtime.IExtension;
47 import org.eclipse.core.runtime.IExtensionPoint;
48 import org.eclipse.core.runtime.IExtensionRegistry;
49 import org.eclipse.core.runtime.IProgressMonitor;
50 import org.eclipse.core.runtime.IStatus;
51 import org.eclipse.core.runtime.InvalidRegistryObjectException;
52 import org.eclipse.core.runtime.OperationCanceledException;
53 import org.eclipse.core.runtime.Platform;
54 import org.eclipse.core.runtime.Status;
55 import org.eclipse.help.internal.base.BaseHelpSystem;
56 import org.eclipse.help.internal.base.HelpBasePlugin;
57 import org.eclipse.help.internal.base.util.HelpProperties;
58 import org.eclipse.help.internal.protocols.HelpURLConnection;
59 import org.eclipse.help.internal.protocols.HelpURLStreamHandler;
60 import org.eclipse.help.internal.toc.TocFileProvider;
61 import org.eclipse.help.internal.toc.TocManager;
62 import org.eclipse.help.internal.util.ResourceLocator;
63 import org.eclipse.help.search.ISearchIndex;
64 import org.eclipse.help.search.LuceneSearchParticipant;
65 import org.osgi.framework.Bundle;
66 import org.osgi.framework.Constants;
67 import org.osgi.framework.Version;
68
69 /**
70  * Text search index. Documents added to this index can than be searched against a search query.
71  */

72 public class SearchIndex implements ISearchIndex {
73
74     private IndexReader ir;
75
76     private IndexWriter iw;
77
78     private File JavaDoc indexDir;
79
80     private String JavaDoc locale;
81
82     private String JavaDoc relativePath;
83
84     private TocManager tocManager;
85
86     private AnalyzerDescriptor analyzerDescriptor;
87
88     private PluginVersionInfo docPlugins;
89
90     // table of all document names, used during indexing batches
91
private HelpProperties indexedDocs;
92
93     public static final String JavaDoc INDEXED_CONTRIBUTION_INFO_FILE = "indexed_contributions"; //$NON-NLS-1$
94

95     public static final String JavaDoc INDEXED_DOCS_FILE = "indexed_docs"; //$NON-NLS-1$
96

97     public static final String JavaDoc DEPENDENCIES_VERSION_FILENAME = "indexed_dependencies"; //$NON-NLS-1$
98

99     public static final String JavaDoc DEPENDENCIES_KEY_LUCENE = "lucene"; //$NON-NLS-1$
100

101     public static final String JavaDoc DEPENDENCIES_KEY_ANALYZER = "analyzer"; //$NON-NLS-1$
102

103     private static final String JavaDoc LUCENE_BUNDLE_ID = "org.apache.lucene"; //$NON-NLS-1$
104

105     private static final String JavaDoc FIELD_NAME = "name"; //$NON-NLS-1$
106

107     private static final String JavaDoc FIELD_INDEX_ID = "index_path"; //$NON-NLS-1$
108

109     private File JavaDoc inconsistencyFile;
110
111     private HTMLSearchParticipant htmlSearchParticipant;
112
113     private IndexSearcher searcher;
114
115     private Object JavaDoc searcherCreateLock = new Object JavaDoc();
116
117     private HelpProperties dependencies;
118
119     private boolean closed = false;
120
121     // Collection of searches occuring now
122
private Collection JavaDoc searches = new ArrayList JavaDoc();
123
124     private FileLock JavaDoc lock;
125
126     /**
127      * Constructor.
128      *
129      * @param locale
130      * the locale this index uses
131      * @param analyzerDesc
132      * the analyzer used to index
133      */

134     public SearchIndex(String JavaDoc locale, AnalyzerDescriptor analyzerDesc, TocManager tocManager) {
135         this(new File JavaDoc(HelpBasePlugin.getConfigurationDirectory(), "index/" + locale), //$NON-NLS-1$
136
locale, analyzerDesc, tocManager, null);
137     }
138
139     /**
140      * Alternative constructor that provides index directory.
141      *
142      * @param indexDir
143      * @param locale
144      * @param analyzerDesc
145      * @param tocManager
146      * @since 3.1
147      */

148
149     public SearchIndex(File JavaDoc indexDir, String JavaDoc locale, AnalyzerDescriptor analyzerDesc, TocManager tocManager,
150             String JavaDoc relativePath) {
151         this.locale = locale;
152         this.analyzerDescriptor = analyzerDesc;
153         this.tocManager = tocManager;
154         this.indexDir = indexDir;
155         this.relativePath = relativePath;
156         // System.out.println("Index for a relative path: "+relativePath);
157
inconsistencyFile = new File JavaDoc(indexDir.getParentFile(), locale + ".inconsistent"); //$NON-NLS-1$
158
htmlSearchParticipant = new HTMLSearchParticipant(indexDir.getAbsolutePath());
159         if (!exists()) {
160             try {
161                 if (tryLock()) {
162                     // don't block or unzip when another instance is indexing
163
try {
164                         unzipProductIndex();
165                     } finally {
166                         releaseLock();
167                     }
168                 }
169             } catch (OverlappingFileLockException JavaDoc ofle) {
170                 // another thread in this process is unzipping
171
// should never be here - one index instance per locale exists
172
// in vm
173
}
174         }
175     }
176
177     /**
178      * Indexes one document from a stream. Index has to be open and close outside of this method
179      *
180      * @param name
181      * the document identifier (could be a URL)
182      * @param url
183      * the URL of the document
184      * @return IStatus
185      */

186     public IStatus addDocument(String JavaDoc name, URL JavaDoc url) {
187         try {
188             Document doc = new Document();
189             doc.add(new Field(FIELD_NAME, name, Field.Store.YES, Field.Index.UN_TOKENIZED));
190             addExtraFields(doc);
191             String JavaDoc pluginId = LocalSearchManager.getPluginId(name);
192             if (relativePath != null) {
193                 doc.add(new Field(FIELD_INDEX_ID, relativePath, Field.Store.YES, Field.Index.UN_TOKENIZED));
194             }
195             // NEW: check for the explicit search participant.
196
LuceneSearchParticipant participant = null;
197             HelpURLConnection urlc = new HelpURLConnection(url);
198             String JavaDoc id = urlc.getValue("id"); //$NON-NLS-1$
199
String JavaDoc pid = urlc.getValue("participantId"); //$NON-NLS-1$
200
if (pid != null)
201                 participant = BaseHelpSystem.getLocalSearchManager().getGlobalParticipant(pid);
202             // NEW: check for file extension-based search participant;
203
if (participant == null)
204                 participant = BaseHelpSystem.getLocalSearchManager().getParticipant(pluginId, name);
205             if (participant != null) {
206                 IStatus status = participant.addDocument(this, pluginId, name, url, id, doc);
207                 if (status.getSeverity() == IStatus.OK) {
208                     String JavaDoc filters = doc.get("filters"); //$NON-NLS-1$
209
indexedDocs.put(name, filters != null ? filters : "0"); //$NON-NLS-1$
210
if (id != null)
211                         doc.add(new Field("id", id, Field.Store.YES, Field.Index.NO)); //$NON-NLS-1$
212
if (pid != null)
213                         doc.add(new Field("participantId", pid, Field.Store.YES, Field.Index.NO)); //$NON-NLS-1$
214
iw.addDocument(doc);
215                 }
216                 return status;
217             }
218             // default to html
219
IStatus status = htmlSearchParticipant.addDocument(this, pluginId, name, url, id, doc);
220             if (status.getSeverity() == IStatus.OK) {
221                 String JavaDoc filters = doc.get("filters"); //$NON-NLS-1$
222
indexedDocs.put(name, filters != null ? filters : "0"); //$NON-NLS-1$
223
iw.addDocument(doc);
224             }
225             return status;
226         } catch (IOException JavaDoc e) {
227             return new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID, IStatus.ERROR,
228                     "IO exception occurred while adding document " + name //$NON-NLS-1$
229
+ " to index " + indexDir.getAbsolutePath() + ".", //$NON-NLS-1$ //$NON-NLS-2$
230
e);
231         }
232         catch (Exception JavaDoc e) {
233             return new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID, IStatus.ERROR,
234                     "An unexpected internal error occurred while adding document " //$NON-NLS-1$
235
+ name + " to index " + indexDir.getAbsolutePath() //$NON-NLS-1$
236
+ ".", e); //$NON-NLS-1$
237
}
238     }
239
240     /**
241      * Add any extra fields that need to be added to this document. Subclasses
242      * should override to add more fields.
243      *
244      * @param doc the document to add fields to
245      */

246     protected void addExtraFields(Document doc) {
247     }
248     
249     /**
250      * Starts additions. To be called before adding documents.
251      */

252     public synchronized boolean beginAddBatch(boolean firstOperation) {
253         try {
254             if (iw != null) {
255                 iw.close();
256             }
257             boolean create = false;
258             if (!indexDir.exists() || !isLuceneCompatible() || !isAnalyzerCompatible()
259                     || inconsistencyFile.exists() && firstOperation) {
260                 create = true;
261                 indexDir.mkdirs();
262                 if (!indexDir.exists())
263                     return false; // unable to setup index directory
264
}
265             indexedDocs = new HelpProperties(INDEXED_DOCS_FILE, indexDir);
266             indexedDocs.restore();
267             setInconsistent(true);
268             iw = new IndexWriter(indexDir, analyzerDescriptor.getAnalyzer(), create);
269             iw.setMergeFactor(20);
270             iw.setMaxFieldLength(1000000);
271             return true;
272         } catch (IOException JavaDoc e) {
273             HelpBasePlugin.logError("Exception occurred in search indexing at beginAddBatch.", e); //$NON-NLS-1$
274
return false;
275         }
276     }
277
278     /**
279      * Starts deletions. To be called before deleting documents.
280      */

281     public synchronized boolean beginDeleteBatch() {
282         try {
283             if (ir != null) {
284                 ir.close();
285             }
286             indexedDocs = new HelpProperties(INDEXED_DOCS_FILE, indexDir);
287             indexedDocs.restore();
288             setInconsistent(true);
289             ir = IndexReader.open(indexDir);
290             return true;
291         } catch (IOException JavaDoc e) {
292             HelpBasePlugin.logError("Exception occurred in search indexing at beginDeleteBatch.", e); //$NON-NLS-1$
293
return false;
294         }
295     }
296
297     /**
298      * Starts deletions. To be called before deleting documents.
299      */

300     public synchronized boolean beginRemoveDuplicatesBatch() {
301         try {
302             if (ir != null) {
303                 ir.close();
304             }
305             ir = IndexReader.open(indexDir);
306             return true;
307         } catch (IOException JavaDoc e) {
308             HelpBasePlugin.logError("Exception occurred in search indexing at beginDeleteBatch.", e); //$NON-NLS-1$
309
return false;
310         }
311     }
312
313     /**
314      * Deletes a single document from the index.
315      *
316      * @param name -
317      * document name
318      * @return IStatus
319      */

320     public IStatus removeDocument(String JavaDoc name) {
321         Term term = new Term(FIELD_NAME, name);
322         try {
323             ir.deleteDocuments(term);
324             indexedDocs.remove(name);
325         } catch (IOException JavaDoc e) {
326             return new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID, IStatus.ERROR,
327                     "IO exception occurred while removing document " + name //$NON-NLS-1$
328
+ " from index " + indexDir.getAbsolutePath() + ".", //$NON-NLS-1$ //$NON-NLS-2$
329
e);
330         }
331         return Status.OK_STATUS;
332     }
333
334     /**
335      * Finish additions. To be called after adding documents.
336      */

337     public synchronized boolean endAddBatch(boolean optimize, boolean lastOperation) {
338         try {
339             if (iw == null)
340                 return false;
341             if (optimize)
342                 iw.optimize();
343             iw.close();
344             iw = null;
345             // save the update info:
346
// - all the docs
347
// - plugins (and their version) that were indexed
348
getDocPlugins().save();
349             saveDependencies();
350             if (lastOperation) {
351                 indexedDocs.save();
352                 indexedDocs = null;
353                 setInconsistent(false);
354             }
355             
356             /*
357              * The searcher's index reader has it's stuff in memory so it won't
358              * know about this change. Close it so that it gets reloaded next search.
359              */

360             if (searcher != null) {
361                 searcher.close();
362                 searcher = null;
363             }
364             return true;
365         } catch (IOException JavaDoc e) {
366             HelpBasePlugin.logError("Exception occurred in search indexing at endAddBatch.", e); //$NON-NLS-1$
367
return false;
368         }
369     }
370
371     /**
372      * Finish deletions. To be called after deleting documents.
373      */

374     public synchronized boolean endDeleteBatch() {
375         try {
376             if (ir == null)
377                 return false;
378             ir.close();
379             ir = null;
380             // save the update info:
381
// - all the docs
382
// - plugins (and their version) that were indexed
383
indexedDocs.save();
384             indexedDocs = null;
385             getDocPlugins().save();
386             saveDependencies();
387             
388             /*
389              * The searcher's index reader has it's stuff in memory so it won't
390              * know about this change. Close it so that it gets reloaded next search.
391              */

392             if (searcher != null) {
393                 searcher.close();
394                 searcher = null;
395             }
396             return true;
397         } catch (IOException JavaDoc e) {
398             HelpBasePlugin.logError("Exception occurred in search indexing at endDeleteBatch.", e); //$NON-NLS-1$
399
return false;
400         }
401     }
402
403     /**
404      * Finish deletions. To be called after deleting documents.
405      */

406     public synchronized boolean endRemoveDuplicatesBatch() {
407         try {
408             if (ir == null)
409                 return false;
410             ir.close();
411             ir = null;
412             // save the update info:
413
// - all the docs
414
// - plugins (and their version) that were indexed
415
indexedDocs.save();
416             indexedDocs = null;
417             getDocPlugins().save();
418             saveDependencies();
419             setInconsistent(false);
420             return true;
421         } catch (IOException JavaDoc e) {
422             HelpBasePlugin.logError("Exception occurred in search indexing at endDeleteBatch.", e); //$NON-NLS-1$
423
return false;
424         }
425     }
426
427     /**
428      * If
429      *
430      * @param dirs
431      * @param monitor
432      * @return Map. Keys are /pluginid/href of all merged Docs. Values are null for added document,
433      * or String[] of indexIds with duplicates of the document
434      */

435     public Map JavaDoc merge(PluginIndex[] pluginIndexes, IProgressMonitor monitor) {
436         ArrayList JavaDoc dirList = new ArrayList JavaDoc(pluginIndexes.length);
437         Map JavaDoc mergedDocs = new HashMap JavaDoc();
438         // Create directories to merge and calculate all documents added
439
// and which are duplicates (to delete later)
440
for (int p = 0; p < pluginIndexes.length; p++) {
441             List JavaDoc indexIds = pluginIndexes[p].getIDs();
442             List JavaDoc indexPaths = pluginIndexes[p].getPaths();
443             if (monitor.isCanceled()) {
444                 throw new OperationCanceledException();
445             }
446
447             for (int i = 0; i < indexPaths.size(); i++) {
448                 String JavaDoc indexId = (String JavaDoc) indexIds.get(i);
449                 String JavaDoc indexPath = (String JavaDoc) indexPaths.get(i);
450                 try {
451                     dirList.add(FSDirectory.getDirectory(indexPath, false));
452                 } catch (IOException JavaDoc ioe) {
453                     HelpBasePlugin
454                             .logError(
455                                     "Help search indexing directory could not be created for directory " + indexPath, ioe); //$NON-NLS-1$
456
continue;
457                 }
458
459                 HelpProperties prebuiltDocs = new HelpProperties(INDEXED_DOCS_FILE, new File JavaDoc(indexPath));
460                 prebuiltDocs.restore();
461                 Set JavaDoc prebuiltHrefs = prebuiltDocs.keySet();
462                 for (Iterator JavaDoc it = prebuiltHrefs.iterator(); it.hasNext();) {
463                     String JavaDoc href = (String JavaDoc) it.next();
464                     if (i == 0) {
465                         // optimization for first prebuilt index of a plug-in
466
mergedDocs.put(href, null);
467                     } else {
468                         if (mergedDocs.containsKey(href)) {
469                             // this is duplicate
470
String JavaDoc[] dups = (String JavaDoc[]) mergedDocs.get(href);
471                             if (dups == null) {
472                                 // first duplicate
473
mergedDocs.put(href, new String JavaDoc[] { indexId });
474                             } else {
475                                 // next duplicate
476
String JavaDoc[] newDups = new String JavaDoc[dups.length + 1];
477                                 System.arraycopy(dups, 0, newDups, 0, dups.length);
478                                 newDups[dups.length] = indexId;
479                                 mergedDocs.put(href, newDups);
480                             }
481                         } else {
482                             // document does not exist in more specific indexes
483
// for this plugin
484
mergedDocs.put(href, null);
485                         }
486
487                     }
488                 }
489             }
490         }
491         // perform actual merging
492
for (Iterator JavaDoc it = mergedDocs.keySet().iterator(); it.hasNext();) {
493             indexedDocs.put(it.next(), "0"); //$NON-NLS-1$
494
}
495         Directory[] luceneDirs = (Directory[]) dirList.toArray(new Directory[dirList.size()]);
496         try {
497             iw.addIndexes(luceneDirs);
498         } catch (IOException JavaDoc ioe) {
499             HelpBasePlugin.logError("Merging search indexes failed.", ioe); //$NON-NLS-1$
500
return new HashMap JavaDoc();
501         }
502         return mergedDocs;
503     }
504
505     public IStatus removeDuplicates(String JavaDoc name, String JavaDoc[] index_paths) {
506         TermDocs hrefDocs = null;
507         TermDocs indexDocs = null;
508         Term hrefTerm = new Term(FIELD_NAME, name);
509         try {
510             for (int i = 0; i < index_paths.length; i++) {
511                 Term indexTerm = new Term(FIELD_INDEX_ID, index_paths[i]);
512                 if (i == 0) {
513                     hrefDocs = ir.termDocs(hrefTerm);
514                     indexDocs = ir.termDocs(indexTerm);
515                 } else {
516                     hrefDocs.seek(hrefTerm);
517                     indexDocs.seek(indexTerm);
518                 }
519                 removeDocuments(hrefDocs, indexDocs);
520             }
521         } catch (IOException JavaDoc ioe) {
522             return new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID, IStatus.ERROR,
523                     "IO exception occurred while removing duplicates of document " + name //$NON-NLS-1$
524
+ " from index " + indexDir.getAbsolutePath() + ".", //$NON-NLS-1$ //$NON-NLS-2$
525
ioe);
526         } finally {
527             if (hrefDocs != null) {
528                 try {
529                     hrefDocs.close();
530                 } catch (IOException JavaDoc e) {
531                 }
532             }
533             if (indexDocs != null) {
534                 try {
535                     indexDocs.close();
536                 } catch (IOException JavaDoc e) {
537                 }
538             }
539         }
540         return Status.OK_STATUS;
541     }
542
543     /**
544      * Removes documents containing term1 and term2
545      *
546      * @param doc1
547      * @param docs2
548      * @throws IOException
549      */

550     private void removeDocuments(TermDocs doc1, TermDocs docs2) throws IOException JavaDoc {
551         if (!doc1.next()) {
552             return;
553         }
554         if (!docs2.next()) {
555             return;
556         }
557         while (true) {
558             if (doc1.doc() < docs2.doc()) {
559                 if (!doc1.skipTo(docs2.doc())) {
560                     if (!doc1.next()) {
561                         return;
562                     }
563                 }
564             } else if (doc1.doc() > docs2.doc()) {
565                 if (!docs2.skipTo(doc1.doc())) {
566                     if (!doc1.next()) {
567                         return;
568                     }
569                 }
570             }
571             if (doc1.doc() == docs2.doc()) {
572                 ir.deleteDocument(doc1.doc());
573                 if (!doc1.next()) {
574                     return;
575                 }
576                 if (!docs2.next()) {
577                     return;
578                 }
579             }
580         }
581     }
582
583     /**
584      * Checks if index exists and is usable.
585      *
586      * @return true if index exists
587      */

588     public boolean exists() {
589         return indexDir.exists() && !isInconsistent();
590         // assume index exists if directory does
591
}
592
593     /**
594      * Performs a query search on this index
595      */

596     public void search(ISearchQuery searchQuery, ISearchHitCollector collector)
597             throws QueryTooComplexException {
598         try {
599             if (closed)
600                 return;
601             registerSearch(Thread.currentThread());
602             if (closed)
603                 return;
604             QueryBuilder queryBuilder = new QueryBuilder(searchQuery.getSearchWord(), analyzerDescriptor);
605             Query luceneQuery = queryBuilder.getLuceneQuery(searchQuery.getFieldNames(), searchQuery
606                     .isFieldSearch());
607             String JavaDoc highlightTerms = queryBuilder.gethighlightTerms();
608             if (luceneQuery != null) {
609                 if (searcher == null) {
610                     openSearcher();
611                 }
612                 Hits hits = searcher.search(luceneQuery);
613                 collector.addHits(LocalSearchManager.asList(hits), highlightTerms);
614             }
615         } catch (BooleanQuery.TooManyClauses tmc) {
616             throw new QueryTooComplexException();
617         } catch (QueryTooComplexException qe) {
618             throw qe;
619         } catch (Exception JavaDoc e) {
620             HelpBasePlugin.logError("Exception occurred performing search for: " //$NON-NLS-1$
621
+ searchQuery.getSearchWord() + ".", e); //$NON-NLS-1$
622
} finally {
623             unregisterSearch(Thread.currentThread());
624         }
625     }
626
627     public String JavaDoc getLocale() {
628         return locale;
629     }
630
631     /**
632      * Returns the list of all the plugins in this session that have declared a help contribution.
633      */

634     public PluginVersionInfo getDocPlugins() {
635         if (docPlugins == null) {
636             Set JavaDoc totalIds = new HashSet JavaDoc();
637             IExtensionRegistry registry = Platform.getExtensionRegistry();
638             IExtensionPoint extensionPoint = registry.getExtensionPoint(TocFileProvider.EXTENSION_POINT_ID_TOC);
639             IExtension[] extensions = extensionPoint.getExtensions();
640             for (int i=0;i<extensions.length;++i) {
641                 try {
642                     totalIds.add(extensions[i].getNamespaceIdentifier());
643                 }
644                 catch (InvalidRegistryObjectException e) {
645                     // ignore this extension and move on
646
}
647             }
648             Collection JavaDoc additionalPluginIds = BaseHelpSystem.getLocalSearchManager()
649                     .getPluginsWithSearchParticipants();
650             totalIds.addAll(additionalPluginIds);
651             docPlugins = new PluginVersionInfo(INDEXED_CONTRIBUTION_INFO_FILE, totalIds, indexDir, !exists());
652         }
653         return docPlugins;
654     }
655
656     /**
657      * Sets the list of all plug-ns in this session. This method is used for external indexer.
658      *
659      * @param docPlugins
660      */

661     public void setDocPlugins(PluginVersionInfo docPlugins) {
662         this.docPlugins = docPlugins;
663     }
664
665     /**
666      * We use HelpProperties, but a list would suffice. We only need the key values.
667      *
668      * @return HelpProperties, keys are URLs of indexed documents
669      */

670     public HelpProperties getIndexedDocs() {
671         HelpProperties indexedDocs = new HelpProperties(INDEXED_DOCS_FILE, indexDir);
672         if (exists())
673             indexedDocs.restore();
674         return indexedDocs;
675     }
676
677     /**
678      * Gets properties with versions of Lucene plugin and Analyzer used in existing index
679      */

680     private HelpProperties getDependencies() {
681         if (dependencies == null) {
682             dependencies = new HelpProperties(DEPENDENCIES_VERSION_FILENAME, indexDir);
683             dependencies.restore();
684         }
685         return dependencies;
686     }
687
688     private boolean isLuceneCompatible() {
689         String JavaDoc usedLuceneVersion = getDependencies().getProperty(DEPENDENCIES_KEY_LUCENE);
690         return isLuceneCompatible(usedLuceneVersion);
691     }
692
693     public boolean isLuceneCompatible(String JavaDoc luceneVersion) {
694         if (luceneVersion==null) return false;
695         String JavaDoc currentLuceneVersion = ""; //$NON-NLS-1$
696
Bundle luceneBundle = Platform.getBundle(LUCENE_BUNDLE_ID);
697         if (luceneBundle != null) {
698             currentLuceneVersion += (String JavaDoc) luceneBundle.getHeaders()
699                     .get(Constants.BUNDLE_VERSION);
700         }
701         //Direct comparison
702
if (currentLuceneVersion.equals(luceneVersion))
703             return true;
704         Version version = new Version(currentLuceneVersion);
705         Version currentVersion = new Version(luceneVersion);
706         // must not compare with the qualifier because they
707
// change from build to build
708
return version.getMajor() == currentVersion.getMajor()
709                 && version.getMinor() == currentVersion.getMinor()
710                 && version.getMicro() == currentVersion.getMicro();
711     }
712
713     private boolean isAnalyzerCompatible() {
714         String JavaDoc usedAnalyzer = getDependencies().getProperty(DEPENDENCIES_KEY_ANALYZER);
715         return isAnalyzerCompatible(usedAnalyzer);
716     }
717
718     public boolean isAnalyzerCompatible(String JavaDoc analyzerId) {
719         if (analyzerId == null) {
720             analyzerId = ""; //$NON-NLS-1$
721
}
722         return analyzerDescriptor.isCompatible(analyzerId);
723     }
724
725     /**
726      * Saves Lucene version and analyzer identifier to a file.
727      */

728     private void saveDependencies() {
729         getDependencies().put(DEPENDENCIES_KEY_ANALYZER, analyzerDescriptor.getId());
730         Bundle luceneBundle = Platform.getBundle(LUCENE_BUNDLE_ID);
731         if (luceneBundle != null) {
732             String JavaDoc luceneBundleVersion = "" //$NON-NLS-1$
733
+ luceneBundle.getHeaders().get(Constants.BUNDLE_VERSION);
734             getDependencies().put(DEPENDENCIES_KEY_LUCENE, luceneBundleVersion);
735         } else {
736             getDependencies().put(DEPENDENCIES_KEY_LUCENE, ""); //$NON-NLS-1$
737
}
738         getDependencies().save();
739     }
740
741     /**
742      * @return Returns true if index has been left in inconsistent state If analyzer has changed to
743      * incompatible one, index is treated as inconsistent as well.
744      */

745     public boolean isInconsistent() {
746         if (inconsistencyFile.exists()) {
747             return true;
748         }
749         return !isLuceneCompatible() || !isAnalyzerCompatible();
750     }
751
752     /**
753      * Writes or deletes inconsistency flag file
754      */

755     public void setInconsistent(boolean inconsistent) {
756         if (inconsistent) {
757             try {
758                 // parent directory already created by beginAddBatch on new
759
// index
760
FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(inconsistencyFile);
761                 fos.close();
762             } catch (IOException JavaDoc ioe) {
763             }
764         } else
765             inconsistencyFile.delete();
766     }
767
768     public void openSearcher() throws IOException JavaDoc {
769         synchronized (searcherCreateLock) {
770             if (searcher == null) {
771                 searcher = new IndexSearcher(indexDir.getAbsolutePath());
772             }
773         }
774     }
775
776     /**
777      * Closes IndexReader used by Searcher. Should be called on platform shutdown, or when TOCs have
778      * changed when no more reading from this index is to be performed.
779      */

780     public void close() {
781         closed = true;
782         // wait for all sarches to finish
783
synchronized (searches) {
784             while (searches.size() > 0) {
785                 try {
786                     Thread.sleep(50);
787                 } catch (InterruptedException JavaDoc ie) {
788                 }
789             }
790             //
791
if (searcher != null) {
792                 try {
793                     searcher.close();
794                 } catch (IOException JavaDoc ioe) {
795                 }
796             }
797         }
798     }
799
800     /**
801      * Finds and unzips prebuild index specified in preferences
802      */

803     private void unzipProductIndex() {
804         String JavaDoc indexPluginId = HelpBasePlugin.getDefault().getPluginPreferences().getString("productIndex"); //$NON-NLS-1$
805
if (indexPluginId == null || indexPluginId.length() <= 0) {
806             return;
807         }
808         InputStream JavaDoc zipIn = ResourceLocator.openFromPlugin(indexPluginId, "doc_index.zip", getLocale()); //$NON-NLS-1$
809
if (zipIn == null) {
810             return;
811         }
812         setInconsistent(true);
813         cleanOldIndex();
814         byte[] buf = new byte[8192];
815         File JavaDoc destDir = indexDir;
816         ZipInputStream JavaDoc zis = new ZipInputStream JavaDoc(zipIn);
817         FileOutputStream JavaDoc fos = null;
818         try {
819             ZipEntry JavaDoc zEntry;
820             while ((zEntry = zis.getNextEntry()) != null) {
821                 // if it is empty directory, create it
822
if (zEntry.isDirectory()) {
823                     new File JavaDoc(destDir, zEntry.getName()).mkdirs();
824                     continue;
825                 }
826                 // if it is a file, extract it
827
String JavaDoc filePath = zEntry.getName();
828                 int lastSeparator = filePath.lastIndexOf("/"); //$NON-NLS-1$
829
String JavaDoc fileDir = ""; //$NON-NLS-1$
830
if (lastSeparator >= 0) {
831                     fileDir = filePath.substring(0, lastSeparator);
832                 }
833                 // create directory for a file
834
new File JavaDoc(destDir, fileDir).mkdirs();
835                 // write file
836
File JavaDoc outFile = new File JavaDoc(destDir, filePath);
837                 fos = new FileOutputStream JavaDoc(outFile);
838                 int n = 0;
839                 while ((n = zis.read(buf)) >= 0) {
840                     fos.write(buf, 0, n);
841                 }
842                 fos.close();
843             }
844             setInconsistent(false);
845         } catch (IOException JavaDoc ioe) {
846             if (fos != null) {
847                 try {
848                     fos.close();
849                 } catch (IOException JavaDoc ioe2) {
850                 }
851             }
852         } finally {
853             try {
854                 zipIn.close();
855                 if (zis != null)
856                     zis.close();
857             } catch (IOException JavaDoc ioe) {
858             }
859         }
860     }
861
862     /**
863      * Cleans any old index and Lucene lock files by initializing a new index.
864      */

865     private void cleanOldIndex() {
866         IndexWriter cleaner = null;
867         try {
868             cleaner = new IndexWriter(indexDir, analyzerDescriptor.getAnalyzer(), true);
869         } catch (IOException JavaDoc ioe) {
870         } finally {
871             try {
872                 if (cleaner != null)
873                     cleaner.close();
874             } catch (IOException JavaDoc ioe) {
875             }
876         }
877     }
878
879     /**
880      * Returns true when the index must be updated.
881      */

882     public synchronized boolean needsUpdating() {
883         if (!exists()) {
884             return true;
885         }
886         return getDocPlugins().detectChange();
887     }
888
889     /**
890      * @return Returns the tocManager.
891      */

892     public TocManager getTocManager() {
893         return tocManager;
894     }
895
896     private void registerSearch(Thread JavaDoc t) {
897         synchronized (searches) {
898             searches.add(t);
899         }
900     }
901
902     private void unregisterSearch(Thread JavaDoc t) {
903         synchronized (searches) {
904             searches.remove(t);
905         }
906     }
907
908     /**
909      * @return Returns the closed.
910      */

911     public boolean isClosed() {
912         return closed;
913     }
914
915     /**
916      * @return true if lock obtained for this Eclipse instance
917      * @throws OverlappingFileLockException
918      * if lock already obtained
919      */

920     public synchronized boolean tryLock() throws OverlappingFileLockException JavaDoc {
921         if (lock != null) {
922             throw new OverlappingFileLockException JavaDoc();
923         }
924         File JavaDoc lockFile = getLockFile();
925         lockFile.getParentFile().mkdirs();
926         try {
927             RandomAccessFile JavaDoc raf = new RandomAccessFile JavaDoc(lockFile, "rw"); //$NON-NLS-1$
928
FileLock JavaDoc l = raf.getChannel().tryLock();
929             if (l != null) {
930                 lock = l;
931                 return true;
932             }
933         } catch (IOException JavaDoc ioe) {
934             lock = null;
935         }
936         return false;
937     }
938
939     private File JavaDoc getLockFile() {
940         return new File JavaDoc(indexDir.getParentFile(), locale + ".lock"); //$NON-NLS-1$
941
}
942
943     /**
944      * Deletes the lock file. The lock must be released prior to this call.
945      *
946      * @return <code>true</code> if the file has been deleted, <code>false</code> otherwise.
947      */

948
949     public synchronized boolean deleteLockFile() {
950         if (lock != null)
951             return false;
952         File JavaDoc lockFile = getLockFile();
953         if (lockFile.exists())
954             return lockFile.delete();
955         return true;
956     }
957
958     public synchronized void releaseLock() {
959         if (lock != null) {
960             try {
961                 lock.channel().close();
962             } catch (IOException JavaDoc ioe) {
963             }
964             lock = null;
965         }
966     }
967
968     public static String JavaDoc getIndexableHref(String JavaDoc url) {
969         String JavaDoc fileName = url.toLowerCase(Locale.ENGLISH);
970         if (fileName.endsWith(".htm") //$NON-NLS-1$
971
|| fileName.endsWith(".html") //$NON-NLS-1$
972
|| fileName.endsWith(".xhtml") //$NON-NLS-1$
973
|| fileName.endsWith(".xml") //$NON-NLS-1$
974
|| fileName.endsWith(".txt")) { //$NON-NLS-1$
975
// indexable
976
} else if (fileName.indexOf(".htm#") >= 0 //$NON-NLS-1$
977
|| fileName.indexOf(".html#") >= 0 //$NON-NLS-1$
978
|| fileName.indexOf(".xhtml#") >= 0 //$NON-NLS-1$
979
|| fileName.indexOf(".xml#") >= 0) { //$NON-NLS-1$
980
url = url.substring(0, url.lastIndexOf('#'));
981             // its a fragment, index whole document
982
} else {
983             // try search participants
984
return BaseHelpSystem.getLocalSearchManager().isIndexable(url) ? url : null;
985         }
986         return url;
987     }
988
989     /**
990      * Checks if document is indexable, and creates a URL to obtain contents.
991      *
992      * @param locale
993      * @param url
994      * specified in the navigation
995      * @return URL to obtain document content or null
996      */

997     public static URL JavaDoc getIndexableURL(String JavaDoc locale, String JavaDoc url) {
998         return getIndexableURL(locale, url, null, null);
999     }
1000
1001    /**
1002     * Checks if document is indexable, and creates a URL to obtain contents.
1003     *
1004     * @param locale
1005     * @param url
1006     * @param participantId
1007     * the search participant or <code>null</code> specified in the navigation
1008     * @return URL to obtain document content or null
1009     */

1010    public static URL JavaDoc getIndexableURL(String JavaDoc locale, String JavaDoc url, String JavaDoc id, String JavaDoc participantId) {
1011        if (participantId == null)
1012            url = getIndexableHref(url);
1013        if (url == null)
1014            return null;
1015
1016        try {
1017            StringBuffer JavaDoc query = new StringBuffer JavaDoc();
1018            query.append("?"); //$NON-NLS-1$
1019
query.append("lang=" + locale); //$NON-NLS-1$
1020
if (id != null)
1021                query.append("&id=" + id); //$NON-NLS-1$
1022
if (participantId != null)
1023                query.append("&participantId=" + participantId); //$NON-NLS-1$
1024
return new URL JavaDoc("help", //$NON-NLS-1$
1025
null, -1, url + query.toString(), HelpURLStreamHandler.getDefault());
1026
1027        } catch (MalformedURLException JavaDoc mue) {
1028            return null;
1029        }
1030    }
1031
1032    public IStatus addDocument(String JavaDoc pluginId, String JavaDoc name, URL JavaDoc url, String JavaDoc id, Document doc) {
1033        // try a registered participant for the file format
1034
LuceneSearchParticipant participant = BaseHelpSystem.getLocalSearchManager()
1035                .getParticipant(pluginId, name);
1036        if (participant != null) {
1037            try {
1038                return participant.addDocument(this, pluginId, name, url, id, doc);
1039            }
1040            catch (Throwable JavaDoc t) {
1041                return new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID, IStatus.ERROR,
1042                        "Error while adding document to search participant (addDocument()): " //$NON-NLS-1$
1043
+ name + ", " + url + "for participant " + participant.getClass().getName(), t); //$NON-NLS-1$ //$NON-NLS-2$
1044
}
1045        }
1046        // default to html
1047
return htmlSearchParticipant.addDocument(this, pluginId, name, url, id, doc);
1048    }
1049}
1050
Popular Tags