diff --git a/src/main/java/org/crosswire/jsword/bridge/BookIndexer.java b/src/main/java/org/crosswire/jsword/bridge/BookIndexer.java index 1f48ee900..333b4774f 100644 --- a/src/main/java/org/crosswire/jsword/bridge/BookIndexer.java +++ b/src/main/java/org/crosswire/jsword/bridge/BookIndexer.java @@ -97,6 +97,26 @@ protected void setDone(boolean state) { done = state; } + public void reindexIfNeeded() throws BookException { + + if(indexManager.needsReindexing(book)) { + createIndex(); + } + } + + + /* //todo static function: Demo of how clients can reindex, after a new install on a computer. If reindex All successful, update Installed.Index.DefaultVersion prop on the client computer + (jsword will reindex only if Latest.Index.Version mandates it, after comparing with Installed.Index.Version) + public static void reindexAllBooksIfNeeded() throws Exception { + + Books myBooks = Books.installed(); + + for(Book insBook: myBooks.getBooks()) { + //reindex if needsReindexing(insBook) true + //manage all Installed.Index.Version property values in metadata file + } + }*/ + protected Book book; protected IndexManager indexManager; private IndexStatusListener isl; diff --git a/src/main/java/org/crosswire/jsword/index/IndexManager.java b/src/main/java/org/crosswire/jsword/index/IndexManager.java index dd09155d9..157c28d78 100644 --- a/src/main/java/org/crosswire/jsword/index/IndexManager.java +++ b/src/main/java/org/crosswire/jsword/index/IndexManager.java @@ -47,6 +47,22 @@ public interface IndexManager { */ Index getIndex(Book book) throws BookException; + /** + * Detect or checking whether this book needs reindexing. + * It is safe methods, you can always call it whether the book + * is already indexed or not. + * This check for
+ *
+     * - isIndexed(Book book)
+     * - Is index valid, eg index version changed incompatibly (due to internal structure change or search engine update)
+     * -
+     * 
+ * + * @param book the Book + * @return true if no index present or current index is of incompatible/older version + */ + boolean needsReindexing(Book book); + /** * Read from the given source version to generate ourselves. On completion * of this method the index should be usable. diff --git a/src/main/java/org/crosswire/jsword/index/lucene/IndexMetadata.java b/src/main/java/org/crosswire/jsword/index/lucene/IndexMetadata.java index 73f7d5b29..7f2e23813 100644 --- a/src/main/java/org/crosswire/jsword/index/lucene/IndexMetadata.java +++ b/src/main/java/org/crosswire/jsword/index/lucene/IndexMetadata.java @@ -24,6 +24,7 @@ import org.crosswire.common.util.PropertyMap; import org.crosswire.common.util.ResourceUtil; +import org.crosswire.jsword.book.Book; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,9 +53,10 @@ private IndexMetadata() { public static IndexMetadata instance() { return myInstance; } - + //default Installed IndexVersion: index version that is installed/available in the Client's index folders + //todo get Installed ver from the IndexFolder location public float getInstalledIndexVersion() { - String value = props.get(INDEX_VERSION, "1.1"); + String value = props.get(INDEX_VERSION, "1.1"); //todo At some point default should be 1.2 return Float.parseFloat(value); } @@ -62,17 +64,38 @@ public float getLuceneVersion() { return Float.parseFloat(props.get(LUCENE_VERSION)); } + //Default Latest IndexVersion : Default version number of Latest indexing schema: PerBook index version must be equal or greater than this public float getLatestIndexVersion() { - String value = props.get(INDEX_VERSION, "1.1"); + String value = props.get(LATEST_INDEX_VERSION, "1.2"); + return Float.parseFloat(value); + } + + public float getLatestIndexVersion(Book b) { + if(b==null) return getLatestIndexVersion(); + + String value = props.get(PREFIX_LATEST_INDEX_VERSION_BOOK_OVERRIDE+b.getBookMetaData().getInitials(), + props.get(LATEST_INDEX_VERSION) ); return Float.parseFloat(value); } + /*public float getInstalledIndexVersion(Book b) { + if(b==null) return getInstalledIndexVersion(); + + String value = props.get(PREFIX_INSTALLED_INDEX_VERSION_BOOK_OVERRIDE +b.getBookMetaData().getInitials(), + props.get(INDEX_VERSION) ); + return Float.parseFloat(value); + }*/ public static final String INDEX_VERSION = "Installed.Index.Version"; public static final String LATEST_INDEX_VERSION = "Latest.Index.Version"; public static final String LUCENE_VERSION = "Lucene.Version"; + + @Deprecated + /* use latest version*/ public static final float INDEX_VERSION_1_1 = 1.1f; public static final float INDEX_VERSION_1_2 = 1.2f; + public static final String PREFIX_LATEST_INDEX_VERSION_BOOK_OVERRIDE = "Latest.Index.Version.Book."; + public static final String PREFIX_INSTALLED_INDEX_VERSION_BOOK_OVERRIDE = "Installed.Index.Version.Book."; private static final Logger log = LoggerFactory.getLogger(IndexMetadata.class); private static IndexMetadata myInstance = new IndexMetadata(); private PropertyMap props; diff --git a/src/main/java/org/crosswire/jsword/index/lucene/InstalledIndex.java b/src/main/java/org/crosswire/jsword/index/lucene/InstalledIndex.java new file mode 100644 index 000000000..64329ecfa --- /dev/null +++ b/src/main/java/org/crosswire/jsword/index/lucene/InstalledIndex.java @@ -0,0 +1,67 @@ +package org.crosswire.jsword.index.lucene; + +import org.crosswire.common.util.PropertyMap; +import org.crosswire.common.util.ResourceUtil; +import org.crosswire.jsword.book.Book; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** +* A singleton that Reads and Maintains Installed Index Metadata (for e.g. version indexed on client) from properties file +* +* @see gnu.lgpl.License for license details.
+* The copyright to this program is held by it's authors. +* @author Sijo Cherian [sijocherian at yahoo dot com] +*/ +public final class InstalledIndex { + public static final String INSTALLED_INDEX_DEFAULT_VERSION = "Installed.Index.DefaultVersion"; + + /** + * All access through this single instance. + * + * @return the singleton instance + */ + public static InstalledIndex instance() { + return myInstance; + } + + public float getInstalledIndexDefaultVersion() { + float toReturn = IndexMetadata.INDEX_VERSION_1_2; //defaultVersionIfNoMetadataFilePresent + String value = props.get(INSTALLED_INDEX_DEFAULT_VERSION); + if(value!=null) + toReturn =Float.parseFloat(value ); + + return toReturn; + } + + public float getInstalledIndexVersion(Book b) { + if(b==null) return getInstalledIndexDefaultVersion(); + //todo change this value on lucene upgrade + float defaultVersionIfNoMetadataFilePresent = IndexMetadata.INDEX_VERSION_1_2; + + String value = props.get(IndexMetadata.PREFIX_INSTALLED_INDEX_VERSION_BOOK_OVERRIDE +b.getBookMetaData().getInitials(), + props.get(INSTALLED_INDEX_DEFAULT_VERSION ) ); + + if(value==null) + return defaultVersionIfNoMetadataFilePresent; + else + return Float.parseFloat(value); + } + + private InstalledIndex() { + try { + + props = ResourceUtil.getProperties(getClass());//, + } catch (IOException e) { + log.error("Property file read error", e); + } + } + + + + private static final Logger log = LoggerFactory.getLogger(InstalledIndex.class); + private static InstalledIndex myInstance = new InstalledIndex(); + private PropertyMap props; +} diff --git a/src/main/java/org/crosswire/jsword/index/lucene/LuceneIndexManager.java b/src/main/java/org/crosswire/jsword/index/lucene/LuceneIndexManager.java index a5141d678..c4e7a72b8 100644 --- a/src/main/java/org/crosswire/jsword/index/lucene/LuceneIndexManager.java +++ b/src/main/java/org/crosswire/jsword/index/lucene/LuceneIndexManager.java @@ -50,6 +50,10 @@ * The copyright to this program is held by it's authors. * @author Joe Walker [joe at eireneh dot com] */ +/* +//todo OPEN questions + use org.apache.lucene.util.Version when upgrading Lucene; + */ public class LuceneIndexManager implements IndexManager { /** * Create a LuceneIndexManager with a default IndexPolicy. @@ -63,6 +67,7 @@ public LuceneIndexManager() { */ public boolean isIndexed(Book book) { try { + if(book==null) return false; URI storage = getStorageArea(book); return NetUtil.isDirectory(storage); } catch (IOException ex) { @@ -90,6 +95,49 @@ public Index getIndex(Book book) throws BookException { } } + /** (non-Javadoc) + * @see org.crosswire.jsword.index.IndexManager#needsReindexing(org.crosswire.jsword.book.Book) + */ + + /* + todo IndexVersion: for new index, store InstalledVersion as getLatestIndexVersion(book) + For any newly created index: + store PerBook prop: Installed.Index.Version.Book.xxx = getLatestIndexVersion(book) + todo Default Installed version Installed.Index.Version [value of getLatestIndexVersion()] in prop file, say ({IndexFolder}/JSword/lucene/js.index.installed.metadata ) + Question: At what point can jsword add/update value of Installed.Index.Version : Options: + 1. If + + */ + public boolean needsReindexing(Book book) { + // step 1: check for existing index + if (!isIndexed(book)) { + return true; + } + + boolean reindex = false; + + // step 2: check for index version + try { + // need hard casting because it is only implemented on LuceneIndex, not the Index interface + //LuceneIndex index = (LuceneIndex)getIndex(book); + + //should Clients use IndexStatus.INVALID + + + float installedV = InstalledIndex.instance().getInstalledIndexVersion(book); + if (installedV < IndexMetadata.instance().getLatestIndexVersion(book)) { + reindex = true; + log.info(book.getName()+": needs reindexing, Installed index version @"+installedV); + } + + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + reindex = true; + } + + return reindex; + } + /* (non-Javadoc) * @see org.crosswire.jsword.index.IndexManager#closeAllIndexes() */ @@ -110,6 +158,10 @@ public void scheduleIndexCreation(final Book book) { try { URI storage = getStorageArea(book); Index index = new LuceneIndex(book, storage, this.policy); + + //todo update Installed IndexVersion for newly created index + // todo implement: Installed.Index.Version.Book.XXX value add/update in metadata file after creation, use value getLatestIndexVersion(book) + // We were successful if the directory exists. if (NetUtil.getAsFile(storage).exists()) { finalStatus = IndexStatus.DONE; @@ -132,6 +184,7 @@ public void installDownloadedIndex(Book book, URI tempDest) throws BookException URI storage = getStorageArea(book); File zip = NetUtil.getAsFile(tempDest); IOUtil.unpackZip(zip, NetUtil.getAsFile(storage)); + //todo Index.Version management?? } catch (IOException ex) { // TRANSLATOR: The search index could not be moved to it's final location. throw new BookException(JSMsg.gettext("Installation failed."), ex); diff --git a/src/main/resources/IndexMetadata.properties b/src/main/resources/IndexMetadata.properties index 40ae8a0e8..281ea9af8 100644 --- a/src/main/resources/IndexMetadata.properties +++ b/src/main/resources/IndexMetadata.properties @@ -17,6 +17,22 @@ # The copyright to this program is held by it's authors. # +# --------------Index Version Summary--------------- +# 1.0 : Original index format. Uses: fields = key,content; Analyzer = SimpleAnalyzer +# 1.1 : Added field = strong, heading, xref, note +# 1.2 : Added natural language analysis (Stemming, CJK tokenization) + + +#should be moved to InstalledIndex.prop Installed.Index.Version=1.2 Latest.Index.Version=1.2 -Lucene.Version=2.3 +Lucene.Version=3.0 + +# Sample values: Book specific index version over-ride # +Latest.Index.Version.Book.ESV=1.2 +Latest.Index.Version.Book.AraSVD=1.2 +Latest.Index.Version.Book.FreSegond=1.2 +Latest.Index.Version.Book.Pilgrim=1.2 +Latest.Index.Version.Book.MHC=1.2 + + diff --git a/src/main/resources/InstalledIndex.properties b/src/main/resources/InstalledIndex.properties new file mode 100644 index 000000000..2bdef44e4 --- /dev/null +++ b/src/main/resources/InstalledIndex.properties @@ -0,0 +1,39 @@ +# Distribution License: +# JSword is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License, version 2.1 or later +# as published by the Free Software Foundation. This program is distributed +# in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# The License is available on the internet at: +# http://www.gnu.org/copyleft/lgpl.html +# or by writing to: +# Free Software Foundation, Inc. +# 59 Temple Place - Suite 330 +# Boston, MA 02111-1307, USA +# +# Copyright: 2005-2011 +# The copyright to this program is held by it's authors. +# + +# --------------This file stays persistent on client machine to indicate the currently indexed version --------------- + +#todo move these prop to appropriate location on client's writable folders ( perhaps {IndexFolder}/JSword/lucene/js.index.metadata file) + +# todo create a version for JUnit testing +# todo where to implement method reindexAllInstalledBooks() ? : If this succeeds, update Installed.Index.DefaultVersion prop on the client computer + +#Default version for all books that are indexed +# After a client calls reindexAllInstalledBooks , update this value+ Book specific values +Installed.Index.DefaultVersion=1.2 + + +#Book specific installed index version over-ride. If the book is not installed at all, then this version value is irrelevant +#todo after each index creation a entry here , after book uninstall remove that book's entry +Installed.Index.Version.Book.ESV=1.2 +Installed.Index.Version.Book.AraSVD=1.2 +Installed.Index.Version.Book.FreSegond=1.2 + +Installed.Index.Version.Book.MHC=1.2 +Installed.Index.Version.Book.Pilgrim=1.2 \ No newline at end of file diff --git a/src/test/java/org/crosswire/jsword/index/lucene/LuceneIndexManagerTest.java b/src/test/java/org/crosswire/jsword/index/lucene/LuceneIndexManagerTest.java new file mode 100644 index 000000000..0e1b4e1ae --- /dev/null +++ b/src/test/java/org/crosswire/jsword/index/lucene/LuceneIndexManagerTest.java @@ -0,0 +1,131 @@ +package org.crosswire.jsword.index.lucene; + +import org.crosswire.common.util.PropertyMap; +import org.crosswire.common.util.ResourceUtil; +import org.crosswire.jsword.book.Book; +import org.crosswire.jsword.book.Books; +import org.crosswire.jsword.index.IndexManagerFactory; +import org.crosswire.jsword.passage.Key; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + + +/** + * Test indexManager responsibilities + * + * + * @author Sijo Cherian + * @see gnu.lgpl.License for license details.
+ * The copyright to this program is held by it's authors. + */ +public class LuceneIndexManagerTest { + + private LuceneIndexManager indexManager; + + @Before + public void setUp() throws Exception { + indexManager = (LuceneIndexManager) IndexManagerFactory.getIndexManager(); + } + + /* Test needsReindexing() method */ + + @Test + public void testInstalledVersionMetadataFileNotExisting() throws Exception { + + Books myBooks = Books.installed(); + boolean performedReindexing = false; + Book reindexedBook = null; + + for (Book insBook : myBooks.getBooks()) { + + if (indexManager.isIndexed(insBook)) { + //todo if(InstalledIndex metadataFile exist) delete it for testing + + if (indexManager.needsReindexing(insBook)) { + System.out.println("Reindexing: " + insBook.getName()); + performedReindexing = true; + reindexedBook = insBook; + indexManager.deleteIndex(insBook); + indexManager.scheduleIndexCreation(insBook); + break; + } + + + } + } //for + + if (performedReindexing) { + assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) >= IndexMetadata.instance().getLatestIndexVersion()); + + //todo After we implement Installed.Index.Version value update in metadata file after reindexing, then assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) == InstalledIndex.instance().getInstalledIndexVersion(reindexedBook) ); + //Can run queries + String myquery = VerseField + ":(john)"; + Key key = reindexedBook.find(myquery); + System.out.println(myquery + " , ResultList: " + key.getName()); + + } + } + + @Test + public void testInstalledVersionEqualToLatestVersion() throws Exception { + + Books myBooks = Books.installed(); + + Book reindexedBook = null; + for (Book insBook : myBooks.getBooks()) { + if (indexManager.isIndexed(insBook)) { + //todo explicitly add metadataFile with Version= LatestVersion value + assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) == InstalledIndex.instance().getInstalledIndexVersion(reindexedBook)); + assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) >= IndexMetadata.instance().getLatestIndexVersion()); + + assertTrue(indexManager.needsReindexing(insBook) == false); + + } + } //for + + } + + // + @Test + public void testInstalledVersionLessThanLatestVersion() throws Exception { + + Books myBooks = Books.installed(); + boolean performedReindexing = false; + Book reindexedBook = null; + for (Book insBook : myBooks.getBooks()) { + + + //todo if(metadataFile exist) update InstalledVersion to a older value + //assertTrue(indexManager.needsReindexing(insBook) == true ); + if (indexManager.needsReindexing(insBook)) { + System.out.println("Reindexing: " + insBook.getName()); + performedReindexing = true; + reindexedBook = insBook; + if (indexManager.isIndexed(insBook)) + indexManager.deleteIndex(insBook); + indexManager.scheduleIndexCreation(insBook); + break; + } + + + } //for + + if (performedReindexing) { + assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) >= IndexMetadata.instance().getLatestIndexVersion()); + + //todo After we implement Installed.Index.Version stored in metadata file, then assertTrue(IndexMetadata.instance().getLatestIndexVersion(reindexedBook) == IndexMetadata.instance().getInstalledIndexVersion(reindexedBook) ); + + String myquery = VerseField + ":(john)"; + Key key = reindexedBook.find(myquery); + System.out.println(myquery + " , ResultList: " + key.getName()); + + } + } + + + protected static final String VerseField = "content"; + +} +