Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
LuceneIndexManager |
|
| 3.1818181818181817;3.182 |
1 | /** | |
2 | * Distribution License: | |
3 | * JSword is free software; you can redistribute it and/or modify it under | |
4 | * the terms of the GNU Lesser General Public License, version 2.1 or later | |
5 | * as published by the Free Software Foundation. This program is distributed | |
6 | * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | |
7 | * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
8 | * See the GNU Lesser General Public License for more details. | |
9 | * | |
10 | * The License is available on the internet at: | |
11 | * http://www.gnu.org/copyleft/lgpl.html | |
12 | * or by writing to: | |
13 | * Free Software Foundation, Inc. | |
14 | * 59 Temple Place - Suite 330 | |
15 | * Boston, MA 02111-1307, USA | |
16 | * | |
17 | * © CrossWire Bible Society, 2005 - 2016 | |
18 | * | |
19 | */ | |
20 | package org.crosswire.jsword.index.lucene; | |
21 | ||
22 | import java.io.File; | |
23 | import java.io.IOException; | |
24 | import java.net.URI; | |
25 | import java.util.HashMap; | |
26 | import java.util.Map; | |
27 | ||
28 | import org.crosswire.common.util.CWProject; | |
29 | import org.crosswire.common.util.FileUtil; | |
30 | import org.crosswire.common.util.IOUtil; | |
31 | import org.crosswire.common.util.NetUtil; | |
32 | import org.crosswire.common.util.Reporter; | |
33 | import org.crosswire.jsword.JSMsg; | |
34 | import org.crosswire.jsword.book.Book; | |
35 | import org.crosswire.jsword.book.BookException; | |
36 | import org.crosswire.jsword.book.BookMetaData; | |
37 | import org.crosswire.jsword.index.Index; | |
38 | import org.crosswire.jsword.index.IndexManager; | |
39 | import org.crosswire.jsword.index.IndexPolicy; | |
40 | import org.crosswire.jsword.index.IndexPolicyAdapter; | |
41 | import org.crosswire.jsword.index.IndexStatus; | |
42 | import org.slf4j.Logger; | |
43 | import org.slf4j.LoggerFactory; | |
44 | ||
45 | /* | |
46 | //todo | |
47 | use org.apache.lucene.util.Version when upgrading Lucene; | |
48 | ||
49 | OPEN questions | |
50 | */ | |
51 | /** | |
52 | * An implementation of IndexManager for Lucene indexes. | |
53 | * | |
54 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
55 | * @author Joe Walker | |
56 | */ | |
57 | 0 | public class LuceneIndexManager implements IndexManager { |
58 | /** | |
59 | * Create a LuceneIndexManager with a default IndexPolicy. | |
60 | */ | |
61 | 0 | public LuceneIndexManager() { |
62 | 0 | policy = new IndexPolicyAdapter(); |
63 | try { | |
64 | 0 | baseFolderURI = CWProject.instance().getWritableProjectSubdir(DIR_LUCENE, false); |
65 | 0 | } catch (IOException ex) { |
66 | 0 | log.error("Failed to find lucene index storage area.", ex); |
67 | ||
68 | 0 | } |
69 | 0 | } |
70 | ||
71 | /* (non-Javadoc) | |
72 | * @see org.crosswire.jsword.index.IndexManager#isIndexed(org.crosswire.jsword.book.Book) | |
73 | */ | |
74 | public boolean isIndexed(Book book) { | |
75 | try { | |
76 | 0 | if (book == null) { |
77 | 0 | return false; |
78 | } | |
79 | 0 | URI storage = getStorageArea(book); |
80 | 0 | return NetUtil.isDirectory(storage); |
81 | 0 | } catch (IOException ex) { |
82 | 0 | log.error("Failed to find lucene index storage area.", ex); |
83 | 0 | return false; |
84 | } | |
85 | } | |
86 | ||
87 | /* (non-Javadoc) | |
88 | * @see org.crosswire.jsword.index.IndexManager#getIndex(org.crosswire.jsword.book.Book) | |
89 | */ | |
90 | public Index getIndex(Book book) throws BookException { | |
91 | try { | |
92 | 0 | Index reply = INDEXES.get(book); |
93 | 0 | if (reply == null) { |
94 | 0 | URI storage = getStorageArea(book); |
95 | 0 | reply = new LuceneIndex(book, storage); |
96 | 0 | INDEXES.put(book, reply); |
97 | } | |
98 | ||
99 | 0 | return reply; |
100 | 0 | } catch (IOException ex) { |
101 | // TRANSLATOR: Common error condition: Some error happened while opening a search index. | |
102 | 0 | throw new BookException(JSMsg.gettext("Failed to initialize Lucene search engine."), ex); |
103 | } | |
104 | } | |
105 | ||
106 | /** | |
107 | * Clients can use this to determine if book's index is stale and needs to re-indexed or downloaded. | |
108 | * Assumes index exists: Client must use isIndexed() prior to using this method | |
109 | * | |
110 | * @return true, if Latest.Index.Version.xxx > Installed.Index.Version.xxx | |
111 | * @see org.crosswire.jsword.index.IndexManager#needsReindexing(org.crosswire.jsword.book.Book) | |
112 | */ | |
113 | public boolean needsReindexing(Book book) { | |
114 | //check for index version | |
115 | //should Clients use IndexStatus.INVALID | |
116 | 0 | float installedV = InstalledIndex.instance().getInstalledIndexVersion(book); |
117 | 0 | if (installedV < IndexMetadata.instance().getLatestIndexVersion(book)) { |
118 | 0 | log.info("{}: needs reindexing, Installed index version @{}", book.getBookMetaData().getInitials(), Float.toString(installedV)); |
119 | 0 | return true; |
120 | } | |
121 | 0 | return false; |
122 | } | |
123 | ||
124 | /* (non-Javadoc) | |
125 | * @see org.crosswire.jsword.index.IndexManager#closeAllIndexes() | |
126 | */ | |
127 | public void closeAllIndexes() { | |
128 | 0 | for (Index index : INDEXES.values()) { |
129 | 0 | index.close(); |
130 | } | |
131 | 0 | } |
132 | ||
133 | /* (non-Javadoc) | |
134 | * @see org.crosswire.jsword.index.IndexManager#scheduleIndexCreation(org.crosswire.jsword.book.Book) | |
135 | */ | |
136 | public void scheduleIndexCreation(final Book book) { | |
137 | 0 | book.setIndexStatus(IndexStatus.SCHEDULED); |
138 | ||
139 | 0 | IndexStatus finalStatus = IndexStatus.UNDONE; |
140 | ||
141 | try { | |
142 | 0 | URI storage = getStorageArea(book); |
143 | 0 | Index index = new LuceneIndex(book, storage, this.policy); |
144 | ||
145 | //todo update Installed IndexVersion for newly created index | |
146 | // todo implement: Installed.Index.Version.Book.XXX value add/update in metadata file after creation, use value getLatestIndexVersion(book) | |
147 | ||
148 | // We were successful if the directory exists. | |
149 | 0 | if (NetUtil.getAsFile(storage).exists()) { |
150 | 0 | finalStatus = IndexStatus.DONE; |
151 | 0 | INDEXES.put(book, index); |
152 | ||
153 | //update IndexVersion | |
154 | 0 | InstalledIndex.instance().storeLatestVersionAsInstalledIndexMetadata(book); |
155 | } | |
156 | 0 | } catch (IOException e) { |
157 | 0 | Reporter.informUser(LuceneIndexManager.this, e); |
158 | 0 | } catch (BookException e) { |
159 | 0 | Reporter.informUser(LuceneIndexManager.this, e); |
160 | } finally { | |
161 | 0 | book.setIndexStatus(finalStatus); |
162 | 0 | } |
163 | 0 | } |
164 | ||
165 | /* (non-Javadoc) | |
166 | * @see org.crosswire.jsword.index.IndexManager#installDownloadedIndex(org.crosswire.jsword.book.Book, java.net.URI) | |
167 | */ | |
168 | public void installDownloadedIndex(Book book, URI tempDest) throws BookException { | |
169 | try { | |
170 | 0 | URI storage = getStorageArea(book); |
171 | 0 | File zip = NetUtil.getAsFile(tempDest); |
172 | 0 | IOUtil.unpackZip(zip, NetUtil.getAsFile(storage)); |
173 | //todo Index.Version management?? | |
174 | 0 | } catch (IOException ex) { |
175 | // TRANSLATOR: The search index could not be moved to it's final location. | |
176 | 0 | throw new BookException(JSMsg.gettext("Installation failed."), ex); |
177 | 0 | } |
178 | 0 | } |
179 | ||
180 | /* (non-Javadoc) | |
181 | * @see org.crosswire.jsword.index.IndexManager#deleteIndex(org.crosswire.jsword.book.Book) | |
182 | */ | |
183 | public void deleteIndex(Book book) throws BookException { | |
184 | // Lucene can build in the directory that currently exists, | |
185 | // overwriting what is there. So we rename the directory, | |
186 | // mark the operation as success and then try to delete the | |
187 | // directory. | |
188 | 0 | File tempPath = null; |
189 | try { | |
190 | // TODO(joe): This needs some checks that it isn't being used | |
191 | //temporary fix, which closes the index - non-thread safe since someone could theoretically come in and activate this again! | |
192 | 0 | Index index = INDEXES.get(book); |
193 | 0 | if (index != null) { |
194 | 0 | index.close(); |
195 | } | |
196 | ||
197 | 0 | File storage = NetUtil.getAsFile(getStorageArea(book)); |
198 | 0 | String finalCanonicalPath = storage.getCanonicalPath(); |
199 | 0 | tempPath = new File(finalCanonicalPath + '.' + IndexStatus.CREATING.toString()); |
200 | ||
201 | 0 | if (tempPath.exists()) { |
202 | 0 | FileUtil.delete(tempPath); |
203 | } | |
204 | ||
205 | // Issues in at least Windows seem to create issues with reusing a file that's been deleted... | |
206 | 0 | tempPath = new File(finalCanonicalPath + '.' + IndexStatus.CREATING.toString()); |
207 | 0 | if (!storage.renameTo(tempPath)) { |
208 | // TRANSLATOR: Error condition: The index could not be deleted. | |
209 | 0 | throw new BookException(JSMsg.gettext("Failed to delete search index.")); |
210 | } | |
211 | 0 | book.setIndexStatus(IndexStatus.UNDONE); |
212 | ||
213 | //Delete index Version metadata (InstalledIndex) | |
214 | 0 | InstalledIndex.instance().removeFromInstalledIndexMetadata(book); |
215 | ||
216 | 0 | } catch (IOException ex) { |
217 | // TRANSLATOR: Error condition: The index could not be deleted. | |
218 | 0 | throw new BookException(JSMsg.gettext("Failed to delete search index."), ex); |
219 | 0 | } |
220 | ||
221 | 0 | FileUtil.delete(tempPath); |
222 | 0 | } |
223 | ||
224 | /* (non-Javadoc) | |
225 | * @see org.crosswire.jsword.index.IndexManager#getIndexPolicy() | |
226 | */ | |
227 | public IndexPolicy getIndexPolicy() { | |
228 | 0 | return policy; |
229 | } | |
230 | ||
231 | /* (non-Javadoc) | |
232 | * @see org.crosswire.jsword.index.IndexManager#setIndexPolicy(org.crosswire.jsword.index.IndexPolicy) | |
233 | */ | |
234 | public void setIndexPolicy(IndexPolicy policy) { | |
235 | 0 | if (policy != null) { |
236 | 0 | this.policy = policy; |
237 | } else { | |
238 | 0 | this.policy = new IndexPolicyAdapter(); |
239 | } | |
240 | ||
241 | 0 | } |
242 | ||
243 | /** | |
244 | * Determine where an index should be stored | |
245 | * | |
246 | * @param book | |
247 | * The book to be indexed | |
248 | * @return A URI to store stuff in | |
249 | * @throws IOException | |
250 | * If there is a problem in finding where to store stuff | |
251 | */ | |
252 | protected URI getStorageArea(Book book) throws IOException { | |
253 | 0 | BookMetaData bmd = book.getBookMetaData(); |
254 | 0 | String driverName = bmd.getDriverName(); |
255 | 0 | String bookName = bmd.getInitials(); |
256 | ||
257 | 0 | assert driverName != null; |
258 | 0 | assert bookName != null; |
259 | ||
260 | ||
261 | //URI driver = NetUtil.lengthenURI(baseFolderURI, driverName); | |
262 | 0 | return NetUtil.lengthenURI(baseFolderURI, driverName + NetUtil.SEPARATOR + bookName); |
263 | } | |
264 | ||
265 | private IndexPolicy policy; | |
266 | private URI baseFolderURI; | |
267 | ||
268 | /** | |
269 | * The created indexes | |
270 | */ | |
271 | 0 | protected static final Map<Book, Index> INDEXES = new HashMap<Book, Index>(); |
272 | ||
273 | /** | |
274 | * The lucene search index directory | |
275 | */ | |
276 | public static final String DIR_LUCENE = "lucene"; | |
277 | ||
278 | /** | |
279 | * The log stream | |
280 | */ | |
281 | 0 | private static final Logger log = LoggerFactory.getLogger(LuceneIndexManager.class); |
282 | } |