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 }