KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > ftindex > FullTextIndexImpl


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.outerj.daisy.ftindex;
17
18 import org.apache.avalon.framework.activity.Initializable;
19 import org.apache.avalon.framework.activity.Disposable;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.configuration.ConfigurationException;
22 import org.apache.avalon.framework.configuration.Configurable;
23 import org.apache.avalon.framework.service.Serviceable;
24 import org.apache.avalon.framework.service.ServiceManager;
25 import org.apache.avalon.framework.service.ServiceException;
26 import org.apache.avalon.framework.logger.AbstractLogEnabled;
27 import org.apache.lucene.index.IndexWriter;
28 import org.apache.lucene.index.IndexReader;
29 import org.apache.lucene.index.Term;
30 import org.apache.lucene.analysis.standard.StandardAnalyzer;
31 import org.apache.lucene.document.Document;
32 import org.apache.lucene.document.Field;
33 import org.apache.lucene.search.*;
34 import org.apache.lucene.queryParser.ParseException;
35 import org.apache.lucene.queryParser.MultiFieldQueryParser;
36 import org.outerj.daisy.repository.query.QueryException;
37 import org.outerj.daisy.backuplock.SuspendableProcess;
38 import org.outerj.daisy.backuplock.SuspendForBackupRegistrar;
39 import org.outerj.daisy.configutil.PropertyResolver;
40
41 import javax.management.MBeanServer JavaDoc;
42 import javax.management.ObjectName JavaDoc;
43 import java.io.File JavaDoc;
44 import java.io.IOException JavaDoc;
45 import java.util.List JavaDoc;
46 import java.util.ArrayList JavaDoc;
47
48 import EDU.oswego.cs.dl.util.concurrent.Mutex;
49 import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
50
51 /**
52  * @avalon.component version="1.0" name="fulltextindex" lifestyle="singleton"
53  * @avalon.service type="org.outerj.daisy.ftindex.FullTextIndex"
54  */

55 public class FullTextIndexImpl extends AbstractLogEnabled implements FullTextIndex, Initializable, Serviceable,
56         Configurable, FullTextIndexImplMBean, SuspendableProcess, Disposable {
57     private File JavaDoc indexDirectory;
58     private ServiceManager serviceManager;
59     private String JavaDoc indexerStatus;
60     private IndexOptimizeThread indexOptimizeThread = null;
61     private Mutex indexWriteMutex = new Mutex();
62     private Mutex indexSearchMutex = new Mutex();
63     private static final String JavaDoc INDEXER_INACTIVE_MSG = "Indexer idle.";
64     private SuspendForBackupRegistrar suspendForBackupRegistrar;
65     private WriterPreferenceReadWriteLock suspendIndexUpdatesLock = new WriterPreferenceReadWriteLock();
66     
67     private IndexReader indexReader;
68     private IndexSearcher indexSearcher;
69     
70
71     public void configure(Configuration configuration) throws ConfigurationException {
72         String JavaDoc directoryName = PropertyResolver.resolveProperties(configuration.getChild("indexDirectory").getValue());
73         indexDirectory = new File JavaDoc(directoryName);
74         if (!indexDirectory.exists())
75             throw new ConfigurationException("The specified directory does not exist: " + directoryName);
76         if (!indexDirectory.isDirectory())
77             throw new ConfigurationException("The specified directory is not a directory: " + directoryName);
78         getLogger().debug("Using the following as directory to store indexes: " + indexDirectory);
79     }
80
81     /**
82      * @avalon.dependency key="mbeanserver" type="javax.management.MBeanServer"
83      * @avalon.dependency key="suspendForBackupRegistrar" type="org.outerj.daisy.backuplock.SuspendForBackupRegistrar"
84      */

85     public void service(ServiceManager serviceManager) throws ServiceException {
86         this.serviceManager = serviceManager;
87         suspendForBackupRegistrar = (SuspendForBackupRegistrar)serviceManager.lookup("suspendForBackupRegistrar");
88     }
89
90     public void initialize() throws Exception JavaDoc {
91         MBeanServer JavaDoc mbeanServer = (MBeanServer JavaDoc)serviceManager.lookup("mbeanserver");
92         try {
93             mbeanServer.registerMBean(this, new ObjectName JavaDoc("Daisy:name=FullTextIndexer"));
94         } finally {
95             serviceManager.release(mbeanServer);
96         }
97
98         indexerStatus = INDEXER_INACTIVE_MSG;
99
100         suspendForBackupRegistrar.register("Fulltext index", this);
101     }
102
103     public synchronized void dispose() {
104         suspendForBackupRegistrar.unregister(this);
105         serviceManager.release(suspendForBackupRegistrar);
106         if (indexOptimizeThread != null) {
107            try { indexOptimizeThread.join(); } catch (InterruptedException JavaDoc e) {}
108         }
109     }
110
111     public boolean suspendExecution(long msecs) throws InterruptedException JavaDoc {
112         return suspendIndexUpdatesLock.writeLock().attempt(msecs);
113     }
114
115     public void resumeExecution() {
116         suspendIndexUpdatesLock.writeLock().release();
117     }
118
119     private boolean indexExists() {
120         return new File JavaDoc(indexDirectory, "segments").exists();
121     }
122
123     public Hits search(String JavaDoc queryAsString, long branchId, long languageId, boolean searchName, boolean searchContent, boolean searchFields) throws QueryException {
124         if (!indexExists())
125             return new EmptyHits();
126
127         Query query;
128         try {
129             List JavaDoc fieldstoSearch = new ArrayList JavaDoc();
130             if (searchName)
131                 fieldstoSearch.add("name");
132             if (searchContent)
133                 fieldstoSearch.add("content");
134             if (searchFields)
135                 fieldstoSearch.add("fields");
136             query = MultiFieldQueryParser.parse(queryAsString, (String JavaDoc[])fieldstoSearch.toArray(new String JavaDoc[fieldstoSearch.size()]), new StandardAnalyzer());
137             if (branchId != -1 || languageId != -1) {
138                 BooleanQuery limittedQuery = new BooleanQuery();
139                 limittedQuery.add(query, true, false);
140                 if (branchId != -1)
141                     limittedQuery.add(new TermQuery(new Term("BranchID", String.valueOf(branchId))), true, false);
142                 if (languageId != -1)
143                     limittedQuery.add(new TermQuery(new Term("LangID", String.valueOf(languageId))), true, false);
144                 query = limittedQuery;
145             }
146         } catch (ParseException e) {
147             throw new QueryException("Error parsing query.", e);
148         }
149
150         try {
151             indexSearchMutex.acquire();
152         } catch (InterruptedException JavaDoc e1) {
153             throw new RuntimeException JavaDoc(e1);
154         }
155         
156         try {
157             if (this.indexSearcher == null ) {
158                 if (this.indexReader == null )
159                     this.indexReader = IndexReader.open(indexDirectory);
160                 this.indexSearcher = new IndexSearcher(indexReader);
161             }
162             return new LuceneHits(indexSearcher.search(query));
163         } catch (Exception JavaDoc e) {
164             throw new QueryException("Error performing fulltext query.", e);
165         } finally {
166             indexSearchMutex.release();
167         }
168     }
169
170     public void index(long documentId, long branchId, long languageId, String JavaDoc documentName, String JavaDoc content, String JavaDoc fields) throws Exception JavaDoc {
171         suspendIndexUpdatesLock.readLock().acquire();
172         try {
173             boolean indexExists = indexExists();
174
175             indexWriteMutex.acquire();
176             try {
177                 indexerStatus = "Updating index for document ID " + documentId + ", branch ID " + branchId + ", language ID " + languageId;
178
179                 if (getLogger().isDebugEnabled())
180                     getLogger().debug("Indexing content for document ID " + documentId + ", branch ID " + branchId + ", language ID " + languageId);
181
182                 if (indexExists) {
183                     // first delete possible old indexed document parts
184
IndexReader indexReader = IndexReader.open(indexDirectory);
185                     IndexSearcher indexSearcher = new IndexSearcher(indexReader);
186                     BooleanQuery booleanQuery = new BooleanQuery();
187                     booleanQuery.add(new TermQuery(new Term("DocID", String.valueOf(documentId))), true, false);
188                     booleanQuery.add(new TermQuery(new Term("BranchID", String.valueOf(branchId))), true, false);
189                     booleanQuery.add(new TermQuery(new Term("LangID", String.valueOf(languageId))), true, false);
190                     org.apache.lucene.search.Hits hits = indexSearcher.search(booleanQuery);
191                     if (hits.length() == 1) {
192                         indexReader.delete(hits.id(0));
193                     } else if (hits.length() > 1) {
194                         getLogger().error("More then one match in index for document ID " + documentId + ", branch ID " + branchId + ", language ID " + languageId + ". I will delete them all but this is *highly* suspicious.");
195                         for (int i = 0; i < hits.length(); i++) {
196                             indexReader.delete(hits.id(i));
197                         }
198                     }
199                     indexReader.close();
200                 }
201
202                 if (documentName != null || content != null || fields != null) {
203                     // now index new stuff
204
IndexWriter indexWriter = new IndexWriter(indexDirectory, new StandardAnalyzer(), !indexExists);
205
206                     Document luceneDocument = new Document();
207                     luceneDocument.add(Field.Keyword("DocID", String.valueOf(documentId)));
208                     luceneDocument.add(Field.Keyword("BranchID", String.valueOf(branchId)));
209                     luceneDocument.add(Field.Keyword("LangID", String.valueOf(languageId)));
210
211                     if (documentName != null) {
212                         luceneDocument.add(Field.UnStored("name", documentName));
213                     }
214
215                     if (content != null && content.length() > 0) {
216                         luceneDocument.add(Field.UnStored("content", content));
217                     }
218
219                     if (fields != null && fields.length() > 0) {
220                         luceneDocument.add(Field.UnStored("fields", fields));
221                     }
222
223                     indexWriter.addDocument(luceneDocument);
224                     indexWriter.close();
225                 }
226             } finally {
227                 indexerStatus = INDEXER_INACTIVE_MSG;
228
229                 if (getLogger().isDebugEnabled())
230                     getLogger().debug("Finished indexing content for document ID " + documentId + ", branch ID " + branchId + ", language ID " + languageId);
231
232                 indexWriteMutex.release();
233                 
234                 
235             }
236         } catch (Exception JavaDoc e) {
237             throw new Exception JavaDoc("Error while indexing content for document " + documentId, e);
238         } finally {
239             suspendIndexUpdatesLock.readLock().release();
240             if (this.indexSearcher != null) {
241                 IndexSearcher searcher = indexSearcher;
242                 this.indexSearcher = null;
243                 try {
244                     searcher.close();
245                 } catch (IOException JavaDoc e) {}
246             }
247             
248             if (this.indexReader != null ) {
249                 IndexReader reader = indexReader;
250                 this.indexReader = null;
251                 try {
252                     reader.close();
253                 } catch (IOException JavaDoc e) {}
254             }
255         }
256     }
257
258     public synchronized String JavaDoc optimizeIndex() {
259         if (!indexExists())
260             return "No index exists, so cannot optimize it.";
261         else if (indexOptimizeThread != null)
262             return "Index optimization is already running or triggered.";
263         else {
264             indexOptimizeThread = new IndexOptimizeThread();
265             indexOptimizeThread.start();
266             return "Launched index optimization background thread.";
267         }
268     }
269
270     public boolean getIndexExists() {
271         return indexExists();
272     }
273
274     public String JavaDoc getIndexerStatus() {
275         return indexerStatus;
276     }
277
278     public class IndexOptimizeThread extends Thread JavaDoc {
279         public void run() {
280             try {
281                 suspendIndexUpdatesLock.readLock().acquire();
282             } catch (InterruptedException JavaDoc e) {
283                 // server is shutting down
284
return;
285             }
286             try {
287                 IndexWriter indexWriter = null;
288
289                 try {
290                     indexWriteMutex.acquire();
291                 } catch (InterruptedException JavaDoc e) {
292                     indexOptimizeThread = null;
293                     return;
294                 }
295
296                 try {
297                     indexerStatus = "Running index optimization.";
298                     getLogger().info("Starting index optimization.");
299                     indexWriter = new IndexWriter(indexDirectory, new StandardAnalyzer(), false);
300                     indexWriter.optimize();
301                 } catch (IOException JavaDoc e) {
302                     getLogger().error("Error optimizing index.", e);
303                 } finally {
304                     if (indexWriter != null) {
305                         try {
306                             indexWriter.close();
307                             getLogger().info("Index optimization ended.");
308                         } catch (Throwable JavaDoc e) {
309                             getLogger().error("Error closing index writer after index optimization.", e);
310                         }
311                     }
312                     indexerStatus = INDEXER_INACTIVE_MSG;
313                     indexWriteMutex.release();
314                     synchronized(FullTextIndexImpl.this) {
315                         indexOptimizeThread = null;
316                     }
317                 }
318             } finally {
319                 suspendIndexUpdatesLock.readLock().release();
320             }
321         }
322     }
323 }
324
Popular Tags