1 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 ; 42 import javax.management.ObjectName ; 43 import java.io.File ; 44 import java.io.IOException ; 45 import java.util.List ; 46 import java.util.ArrayList ; 47 48 import EDU.oswego.cs.dl.util.concurrent.Mutex; 49 import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock; 50 51 55 public class FullTextIndexImpl extends AbstractLogEnabled implements FullTextIndex, Initializable, Serviceable, 56 Configurable, FullTextIndexImplMBean, SuspendableProcess, Disposable { 57 private File indexDirectory; 58 private ServiceManager serviceManager; 59 private String indexerStatus; 60 private IndexOptimizeThread indexOptimizeThread = null; 61 private Mutex indexWriteMutex = new Mutex(); 62 private Mutex indexSearchMutex = new Mutex(); 63 private static final String 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 directoryName = PropertyResolver.resolveProperties(configuration.getChild("indexDirectory").getValue()); 73 indexDirectory = new File (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 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 { 91 MBeanServer mbeanServer = (MBeanServer )serviceManager.lookup("mbeanserver"); 92 try { 93 mbeanServer.registerMBean(this, new ObjectName ("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 e) {} 108 } 109 } 110 111 public boolean suspendExecution(long msecs) throws InterruptedException { 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 (indexDirectory, "segments").exists(); 121 } 122 123 public Hits search(String 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 fieldstoSearch = new ArrayList (); 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 [])fieldstoSearch.toArray(new String [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 e1) { 153 throw new RuntimeException (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 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 documentName, String content, String fields) throws Exception { 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 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 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 e) { 237 throw new Exception ("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 e) {} 246 } 247 248 if (this.indexReader != null ) { 249 IndexReader reader = indexReader; 250 this.indexReader = null; 251 try { 252 reader.close(); 253 } catch (IOException e) {} 254 } 255 } 256 } 257 258 public synchronized String 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 getIndexerStatus() { 275 return indexerStatus; 276 } 277 278 public class IndexOptimizeThread extends Thread { 279 public void run() { 280 try { 281 suspendIndexUpdatesLock.readLock().acquire(); 282 } catch (InterruptedException e) { 283 return; 285 } 286 try { 287 IndexWriter indexWriter = null; 288 289 try { 290 indexWriteMutex.acquire(); 291 } catch (InterruptedException 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 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 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 |