1 package com.atlassian.bonnie;
2
3 import edu.emory.mathcs.backport.java.util.concurrent.locks.ReadWriteLock;
4 import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantReadWriteLock;
5 import org.apache.log4j.Category;
6 import org.apache.log4j.Level;
7 import org.apache.lucene.analysis.Analyzer;
8 import org.apache.lucene.index.IndexReader;
9 import org.apache.lucene.index.IndexUtils;
10 import org.apache.lucene.index.IndexWriter;
11 import org.apache.lucene.search.IndexSearcher;
12 import org.apache.lucene.store.Directory;
13 import org.apache.lucene.store.FSDirectory;
14
15 import java.io.File;
16 import java.io.FileNotFoundException;
17 import java.io.IOException;
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 public class LuceneConnection implements ILuceneConnection
37 {
38 public static final Category LOG = Category.getInstance(LuceneConnection.class);
39
40 public static final int WRITER_DEFAULT = 0x01;
41 public static final int WRITER_INTERACTIVE = 0x02;
42 public static final int WRITER_BATCH = 0x04;
43
44 public static final String REINDEXING_FILENAME = ".reindexing";
45
46 protected Analyzer analyzerForIndexing;
47 protected boolean searcherOpensNewReader = true;
48
49 private File indexDir = null;
50
51
52
53
54 private Directory directory = null;
55
56 private final ReadWriteLock sync = new ReentrantReadWriteLock();
57 private final Object createLock = new Object();
58
59 private final Configuration configuration;
60
61 private IndexReader reader = null;
62 private IndexWriter writer = null;
63
64 private long readerVersion = -1;
65 private boolean alwaysUnlock = true;
66
67
68
69
70
71 public LuceneConnection()
72 {
73 this.configuration = DEFAULT_CONFIGURATION;
74 }
75
76
77
78
79
80
81
82
83 public LuceneConnection(File location, Analyzer analyzerForIndexing)
84 {
85 this(location, analyzerForIndexing, DEFAULT_CONFIGURATION);
86 }
87
88
89
90
91
92
93
94 public LuceneConnection(File location, Analyzer analyzerForIndexing, Configuration configuration)
95 {
96 this.indexDir = location;
97 this.analyzerForIndexing = analyzerForIndexing;
98 this.configuration = configuration;
99 }
100
101
102
103
104
105
106
107 public LuceneConnection(Directory directory, Analyzer analyzerForIndexing)
108 {
109 this.directory = directory;
110 this.analyzerForIndexing = analyzerForIndexing;
111 this.configuration = DEFAULT_CONFIGURATION;
112 }
113
114
115
116
117
118
119
120
121 public LuceneConnection(Directory directory, Analyzer analyzerForIndexing, Configuration configuration)
122 {
123 this.directory = directory;
124 this.analyzerForIndexing = analyzerForIndexing;
125 this.configuration = configuration;
126 }
127
128
129
130
131 public void withSearch(LuceneConnection.SearcherAction action) throws LuceneException
132 {
133 IndexSearcher searcher = null;
134 grabReadLock();
135 try
136 {
137 searcher = getSearcher();
138 if (action.perform(searcher))
139 {
140 searcher.close();
141 }
142 }
143 catch (Throwable e)
144 {
145 if (searcher != null)
146 {
147 try
148 {
149 searcher.close();
150 }
151 catch (Throwable t)
152 {
153 LOG.debug(t);
154 }
155 }
156 if (e instanceof Error)
157 {
158 throw (Error) e;
159 }
160 if (e instanceof RuntimeException)
161 {
162 throw (RuntimeException) e;
163 }
164 throw new LuceneException(e);
165 }
166 finally
167 {
168 releaseReadLock();
169 }
170 }
171
172 protected IndexSearcher getSearcher() throws IOException
173 {
174 if (searcherOpensNewReader)
175 {
176 return new IndexSearcher(getDirectory());
177 }
178 else
179 {
180 return new IndexSearcher(getReader());
181 }
182 }
183
184
185
186
187 public Object withReader(LuceneConnection.ReaderAction action) throws LuceneException
188 {
189 Object result = null;
190 grabReadLock();
191 try
192 {
193 IndexReader reader = getReader();
194 result = action.perform(reader);
195 }
196 catch (IOException e)
197 {
198 throw new LuceneException(e);
199 }
200 finally
201 {
202 releaseReadLock();
203 }
204 return result;
205 }
206
207
208
209
210 public void withReaderAndDeletes(ReaderAction action) throws LuceneException
211 {
212 grabWriteLock();
213 try
214 {
215 unlockIfNeeded();
216 closeWriter();
217 IndexReader reader = getReader();
218 action.perform(reader);
219 closeReader();
220 }
221 catch (IOException e)
222 {
223
224 throw new LuceneException(e);
225 }
226 finally
227 {
228 releaseWriteLock();
229 }
230 }
231
232
233
234
235 public void withWriter(WriterAction action) throws LuceneException
236 {
237 withWriter(action, WRITER_INTERACTIVE);
238 }
239
240 public void withWriter(WriterAction action, int flags) throws LuceneException
241 {
242 grabWriteLock();
243 try
244 {
245 unlockIfNeeded();
246 IndexWriter writer = getWriter(flags);
247 action.perform(writer);
248 if (!isReIndexing())
249 {
250 closeWriter();
251 }
252 }
253 catch (IOException e)
254 {
255 throw new LuceneException(e);
256 }
257 finally
258 {
259 releaseWriteLock();
260 }
261 }
262
263 public void withDeleteAndWrites(ReaderAction readerAction, WriterAction writerAction) throws LuceneException
264 {
265 grabWriteLock();
266 try
267 {
268 withReaderAndDeletes(readerAction);
269 withWriter(writerAction);
270 }
271 finally
272 {
273 releaseWriteLock();
274 }
275 }
276
277
278
279
280 public void withBatchUpdate(BatchUpdateAction action)
281 {
282 grabWriteLock();
283 try
284 {
285 action.perform();
286 }
287 catch (Exception e)
288 {
289 throw new LuceneException(e);
290 }
291 finally
292 {
293 releaseWriteLock();
294 }
295 }
296
297
298
299
300 public void optimize() throws LuceneException
301 {
302 grabWriteLock();
303 try
304 {
305 IndexWriter writer = getWriter();
306 writer.optimize();
307 closeWriter();
308 }
309 catch (IOException e)
310 {
311 throw new LuceneException(e);
312 }
313 finally
314 {
315 releaseWriteLock();
316 }
317 }
318
319 public void setSearcherOpensNewReader(boolean searcherOpensNewReader)
320 {
321 this.searcherOpensNewReader = searcherOpensNewReader;
322 }
323
324 private IndexWriter createAndConfigureWriter(int flags) throws LuceneException
325 {
326 try
327 {
328 if (LOG.isDebugEnabled())
329 {
330 LOG.debug(Thread.currentThread().getName() + "## opening writer");
331 }
332 IndexWriter writer = new IndexWriter(getDirectory(), analyzerForIndexing, false);
333 configureWriter(writer, flags);
334 return writer;
335 }
336 catch (IOException e)
337 {
338 throw new LuceneException(e);
339 }
340 }
341
342
343
344
345
346 void configureWriter(IndexWriter writer, int flags)
347 {
348 writer.setMaxFieldLength(configuration.getMaxFieldLength());
349 writer.setUseCompoundFile(configuration.isCompoundIndexFileFormat());
350 if (isSet(flags, WRITER_BATCH))
351 {
352 writer.setMergeFactor(configuration.getBatchMergeFactor());
353 writer.setMaxBufferedDocs(configuration.getBatchMaxBufferedDocs());
354 writer.setMaxMergeDocs(configuration.getBatchMaxMergeDocs());
355 }
356 else if (isSet(flags, WRITER_INTERACTIVE))
357 {
358 writer.setMergeFactor(configuration.getInteractiveMergeFactor());
359 writer.setMaxBufferedDocs(configuration.getInteractiveMaxBufferedDocs());
360 writer.setMaxMergeDocs(configuration.getInteractiveMaxMergeDocs());
361 }
362 }
363
364 private static boolean isSet(int flags, int f)
365 {
366 return (flags & f) > 0;
367 }
368
369 public void flushWriter() throws LuceneException
370 {
371 grabWriteLock();
372 try
373 {
374 closeReader();
375 closeWriter();
376 }
377 finally
378 {
379 releaseWriteLock();
380 }
381 }
382
383 protected final IndexReader getReader() throws LuceneException
384 {
385 synchronized (createLock)
386 {
387
388 try
389 {
390 if (reader == null)
391 {
392 createIndexDirectory(false);
393 LOG.debug(Thread.currentThread().getName() + "##opening reader");
394 readerVersion = IndexReader.getCurrentVersion(getDirectory());
395 reader = IndexReader.open(getDirectory());
396 }
397 else
398 {
399 long v = IndexReader.getCurrentVersion(getDirectory());
400 if (v != readerVersion)
401 {
402 createIndexDirectory(false);
403 LOG.debug(Thread.currentThread().getName() + "Refreshing reader");
404
405
406 reader.close();
407 reader = IndexReader.open(getDirectory());
408 readerVersion = v;
409 }
410 }
411 return reader;
412 }
413 catch (IOException e)
414 {
415 throw new LuceneException(e);
416 }
417 }
418 }
419
420 protected IndexWriter getWriter() throws LuceneException
421 {
422 return getWriter(WRITER_DEFAULT);
423 }
424
425 protected IndexWriter getWriter(int flags) throws LuceneException
426 {
427 synchronized (createLock)
428 {
429 createIndexDirectory(false);
430 closeReader();
431 if (writer == null)
432 {
433 writer = createAndConfigureWriter(flags);
434 }
435 return writer;
436 }
437 }
438
439
440
441
442 private void closeWriter()
443 {
444 synchronized (createLock)
445 {
446 if (writer != null)
447 {
448 try
449 {
450 IndexWriter writer = this.writer;
451 this.writer = null;
452 if (LOG.isDebugEnabled())
453 {
454 LOG.debug(Thread.currentThread().getName() + "##closing writer");
455 }
456 writer.close();
457 }
458 catch (IOException e)
459 {
460 LOG.error(Level.ERROR + "problem closing index writer" + e, e);
461 }
462 }
463 }
464 }
465
466
467
468
469 private void closeReader()
470 {
471 synchronized (createLock)
472 {
473 if (reader != null)
474 {
475 try
476 {
477 IndexReader reader = this.reader;
478 this.reader = null;
479 if (LOG.isDebugEnabled())
480 {
481 LOG.debug(Thread.currentThread().getName() + "##closing reader");
482 }
483 reader.close();
484 }
485 catch (IOException e)
486 {
487 LOG.error("problem closing index reader" + e, e);
488 }
489 }
490 }
491 }
492
493 private void grabReadLock() throws LuceneException
494 {
495 sync.readLock().lock();
496 }
497
498 private void releaseReadLock()
499 {
500 sync.readLock().unlock();
501 }
502
503 private void grabWriteLock() throws LuceneException
504 {
505 sync.writeLock().lock();
506 }
507
508 private void releaseWriteLock()
509 {
510 sync.writeLock().unlock();
511 }
512
513
514
515
516 public void close()
517 {
518 try
519 {
520 grabWriteLock();
521 try
522 {
523 closeReader();
524 closeWriter();
525 }
526 finally
527 {
528 releaseWriteLock();
529 }
530 }
531 catch (LuceneException e)
532 {
533 LOG.error("problem closing lucene connection" + e);
534 }
535 }
536
537 public boolean isReIndexing() throws IOException
538 {
539 return getDirectory().fileExists(REINDEXING_FILENAME);
540 }
541
542
543
544
545
546
547
548
549
550 public void setReIndexing(boolean b) throws IOException
551 {
552 if (!b)
553 {
554 if (isReIndexing())
555 {
556 getDirectory().deleteFile(REINDEXING_FILENAME);
557 closeWriter();
558 }
559 }
560 else if (!isReIndexing())
561 {
562 Directory directory = getDirectory();
563 if (!directory.fileExists(REINDEXING_FILENAME))
564 {
565 directory.createOutput(REINDEXING_FILENAME).close();
566 }
567 else
568 {
569 directory.touchFile(REINDEXING_FILENAME);
570 }
571 }
572 }
573
574 public void recreateIndexDirectory()
575 {
576 createIndexDirectory(true);
577 }
578
579 public IndexSearcher leakSearcher()
580 {
581 throw new UnsupportedOperationException("Not Implemented. Use withSearch method instead.");
582 }
583
584
585
586
587
588
589
590
591
592 protected boolean shouldCreateIndexDirectory() throws IOException
593 {
594 return !isIndexCreated();
595 }
596
597
598
599
600
601
602
603
604 private void createIndexDirectory(boolean forceCreate)
605 {
606 try
607 {
608 if (forceCreate || (shouldCreateIndexDirectory()))
609 {
610
611 new IndexWriter(getDirectory(), null, true).close();
612 }
613 }
614 catch (IOException e)
615 {
616 throw new LuceneException("Cannot create index directory", e);
617 }
618 }
619
620 public void truncateIndex() throws IOException
621 {
622 grabWriteLock();
623
624 try
625 {
626 IndexUtils.truncateIndex(getDirectory());
627 }
628 catch(FileNotFoundException e)
629 {
630
631 recreateIndexDirectory();
632 }
633 finally
634 {
635 releaseWriteLock();
636 }
637 }
638
639
640
641
642
643
644 protected Directory getDirectory() throws IOException
645 {
646 if (directory == null)
647 {
648 File indexDir = getIndexDir();
649 if (!indexDir.exists() && !indexDir.mkdir())
650 {
651 throw new IOException("Unable to create index directory '" + indexDir.getAbsolutePath() + "'");
652 }
653 directory = FSDirectory.getDirectory(indexDir);
654 }
655 return directory;
656 }
657
658 public File getIndexDir()
659 {
660 return indexDir;
661 }
662
663 public void setIndexDir(String location)
664 {
665 this.indexDir = new File(location);
666 }
667
668
669
670
671 public int getNumDocs()
672 {
673 int count = -1;
674 try
675 {
676 grabReadLock();
677 count = getReader().numDocs();
678 }
679 finally
680 {
681 releaseReadLock();
682 }
683 return count;
684 }
685
686
687
688
689 public boolean isIndexCreated()
690 {
691 try
692 {
693 return IndexReader.indexExists(getDirectory());
694 }
695 catch (IOException e)
696 {
697 return false;
698 }
699 }
700
701 public void unlockIfNeeded() throws IOException
702 {
703 if (alwaysUnlock)
704 {
705 IndexReader.unlock(getDirectory());
706 }
707 }
708
709 public void setAlwaysUnlock(boolean alwaysUnlock)
710 {
711 this.alwaysUnlock = alwaysUnlock;
712 }
713
714 public class TempIndexWriterData
715 {
716 public final IndexWriter writer;
717 public final Directory dir;
718 public final File tmpIndexDir;
719
720 public TempIndexWriterData(IndexWriter writer, Directory dir, File tmpIndexDir)
721 {
722 this.writer = writer;
723 this.dir = dir;
724 this.tmpIndexDir = tmpIndexDir;
725 }
726 }
727 }