1   package org.apache.lucene.search;
2   
3   /**
4    * Copyright 2004 The Apache Software Foundation
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import org.apache.lucene.index.IndexReader;
20  import org.apache.lucene.store.Directory;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.IOException;
25  
26  /**
27   * Implements search over a single IndexReader, but remains open even if close() is called. This way it can be shared by
28   * multiple objects that need to search the index without being aware of the keep-the-index-open-until-it-changes logic.<br>
29   * <br>
30   * Basic use (works, but can slow all searcher while opening a new Searcher instance:
31   * <pre>
32   * class SearcherFactory {
33   *     SearcherFactory(IndexReader reader){
34   *         currentSearcher=new DelayCloseIndexSearcher(reader);
35   *     }
36   *
37   *     public synchronized IndexSearcher createSearcher() {
38   *         if (!currentSearcher.isCurrent()) {
39   *             currentSearcher.closeWhenDone();
40   *             currentSearcher=new DelayCloseIndexSearcher(reader);
41   *         }
42   *
43   *         currentSearcher.open();
44   *         return currentSearcher;
45   *     }
46   *
47   *     void synchronized close() {
48   *         currentSearcher.closeWhenDone();
49   *     }
50   *
51   *     private final IndexReader reader;
52   *
53   *     private DelayCloseIndexSearcher currentSearcher;
54   * }
55   * </pre>
56   * <p/>
57   * Objects that need to search the index do the following:<br>
58   * <pre>
59   * //searcherFactory is created once at startup
60   * <p/>
61   * IndexSearcher indexSearcher=searcherFactory.createSearcher();
62   * Hits hist=indexSearcher.search(query, filter, sort);
63   * ... // handle results
64   * indexSearcher.close();
65   * <p/>
66   * // when the application shuts down
67   * searcherFactory.close();
68   * </pre>
69   *
70   * @author Luc Vanlerberghe
71   */
72  public class DelayCloseIndexSearcher extends IndexSearcher
73  {
74      private IndexReader indexReader;
75  
76      /**
77       * Creates a DelayCloseIndexSearcher searching the index using the provided Reader
78       *
79       * <b>Important:</b> The passed in IndexReader will be closed once the underlying
80       * IndexSearcher is closed.
81       *
82       * @param indexReader Reader to use for opening the searcher
83       * @throws IOException if an I/O error occurs
84       */
85      public DelayCloseIndexSearcher(IndexReader indexReader) throws IOException
86      {
87          super(indexReader);
88          this.indexReader = indexReader;
89          this.usageCount = 0;
90          this.shouldCloseWhenDone = false;
91  
92          log.debug("<init>");
93      }
94  
95      /**
96       * This should be called whenever this instances is passed as a new IndexSearcher.
97       * Only when each call to open() is balanced with a call to close(), and closeWhenDone has been called,
98       * will super.close() be called.
99       */
100     public void open()
101     {
102         synchronized (this)
103         {
104             if (shouldCloseWhenDone)
105             {
106                 throw new IllegalStateException("closeWhenDone() already called");
107             }
108 
109             ++usageCount;
110         }
111 
112         log.debug("open()");
113     }
114 
115     /**
116      * Signals that this instance may really close when all open() calls have been balanced with a call to close().
117      *
118      * @throws IOException if an I/O error occurs.
119      */
120     public void closeWhenDone() throws IOException
121     {
122         log.debug("closeWhenDone()");
123 
124         boolean doClose; // Keep the actual super.close() out of the synchronized block
125 
126         synchronized (this)
127         {
128             if (shouldCloseWhenDone)
129             {
130                 throw new IllegalStateException("closeWhenDone() already called");
131             }
132 
133             shouldCloseWhenDone = true;
134 
135             doClose = (usageCount == 0);
136         }
137 
138         if (doClose)
139         {
140             closeInternal();
141         }
142     }
143 
144     /**
145      * Returns whether the underlying IndexSearcher instance still works on a current version of the index.
146      * If it returns false, closeWhenDone() should be called and another instance created to handle further search requests.
147      *
148      * @return whether the underlying IndexSearcher instance still works on a current version of the index
149      * @throws IOException if an I/O error occurs.
150      */
151     public boolean isCurrent() throws IOException
152     {
153         return getIndexReader().isCurrent();
154     }
155 
156     /**
157      * Returns wether the underlying IndexSearcher has really been closed.
158      * If it is true, this instance can no longer be used.
159      *
160      * @return whether the underlying IndexSearcher has really been closed.
161      */
162     public boolean isClosed()
163     {
164         synchronized (this)
165         {
166             return shouldCloseWhenDone && usageCount == 0;
167         }
168     }
169 
170     //
171     // IndexSearcher overrides
172     //
173 
174     /**
175      * Should be called once for every call to open().
176      * If the usageCount drops to zero and closeWhenDone() was called, super.close() will be called.
177      *
178      * @throws IOException if an I/O error occurs.
179      */
180     public void close() throws IOException
181     {
182         log.debug("close()");
183 
184         boolean doClose; // Keep the actual super.close() out of the synchronized block
185 
186         synchronized (this)
187         {
188             if (usageCount <= 0)
189             {
190                 throw new IllegalStateException("usageCount<=0");
191             }
192 
193             doClose = (--usageCount == 0 && shouldCloseWhenDone);
194         }
195 
196         if (doClose)
197         {
198             closeInternal();
199         }
200     }
201 
202     private void closeInternal() throws IOException
203     {
204         log.debug("Closing underlying index searcher");
205         super.close();
206 
207         // need to close the indexReader manually because this IndexSearcher was constructed with the constructor
208         // IndexSearcher(IndexReader r) which prevents the passed in the reader from being closed when the searcher is closed.
209         log.debug("Closing underlying index reader");
210         indexReader.close();
211     }
212 
213     //
214     // private members
215     //
216 
217     /**
218      * The number of open() calls minus the number of close() calls.
219      * If this drops to zero and closeWhenDone() is true, super.close() is called.
220      */
221     private int usageCount;
222 
223     /**
224      * Indicates if closeWhenDone() was called.
225      * If true and usageCount is zero, super.close() is called.
226      */
227     private boolean shouldCloseWhenDone;
228 
229     /**
230      * The Logger instance for this Class.
231      */
232     private static final Logger log = LoggerFactory.getLogger(DelayCloseIndexSearcher.class);
233 }