blob: 141ccbb20b9c0833f6b30aaa52f61d217db54cba [file] [log] [blame]
Index: lucene/contrib/facet/src/test/org/apache/lucene/facet/search/SearcherTaxoManagerTest.java
===================================================================
--- lucene/contrib/facet/src/test/org/apache/lucene/facet/search/SearcherTaxoManagerTest.java (revision 0)
+++ lucene/contrib/facet/src/test/org/apache/lucene/facet/search/SearcherTaxoManagerTest.java (working copy)
@@ -0,0 +1,88 @@
+package org.apache.lucene.facet.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.facet.search.SearcherTaxoManager.TaxonomyTimestamp;
+import org.apache.lucene.facet.taxonomy.CategoryPath;
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
+import org.apache.lucene.facet.taxonomy.TaxonomyWriter;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.junit.Test;
+
+public class SearcherTaxoManagerTest extends LuceneTestCase {
+
+ private void touchIndex(Directory searchDir, Directory taxoDir) throws IOException {
+ String timeString = Long.toString(System.currentTimeMillis());
+ TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir);
+ tw.addCategory(new CategoryPath("a/"+Integer.toString(tw.getSize()), '/'));
+ Map<String, String> commitData = new HashMap<String, String>();
+ commitData.put(TaxonomyTimestamp.INDEX_UPDATE_TIME, timeString);
+ commitData.put(TaxonomyTimestamp.INDEX_CREATE_TIME, timeString);
+ tw.commit(commitData);
+ tw.close();
+
+ IndexWriter writer = new IndexWriter(searchDir, new IndexWriterConfig(
+ TEST_VERSION_CURRENT, new WhitespaceAnalyzer(TEST_VERSION_CURRENT)));
+ writer.addDocument(new Document());
+ commitData.clear();
+ commitData.put(TaxonomyTimestamp.TAXO_UPDATE_TIME, timeString);
+ commitData.put(TaxonomyTimestamp.TAXO_CREATE_TIME, timeString);
+ writer.commit(commitData);
+ writer.close();
+ }
+
+ private void touchIndexNonSynched(Directory dir, Directory taxoDir) throws IOException {
+ TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir);
+ tw.addCategory(new CategoryPath("a/"+Integer.toString(tw.getSize()), '/'));
+ tw.commit();
+ tw.close();
+
+ IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(
+ TEST_VERSION_CURRENT, new WhitespaceAnalyzer(TEST_VERSION_CURRENT)));
+ writer.addDocument(new Document());
+ long time = System.nanoTime();
+ // TODO: read createTime from the taxoDir following the commit
+ writer.commit(new TaxonomyTimestamp(-1, time + 1000).toSearchCommitData());
+ writer.close();
+ }
+
+ @Test(expected=IOException.class)
+ public void testCtorWithNonSynchedIndexes() throws Exception {
+ Directory dir = newDirectory();
+ Directory taxoDir = newDirectory();
+ touchIndexNonSynched(dir, taxoDir);
+ // assert just to avoid the "object not used" compile warning...
+ assertNotNull(new SearcherTaxoManager(dir, taxoDir, null));
+ fail("should not have reached here - SearcherTaxoManager should fail if search and taxonomy indexes are out of sync");
+ }
+
+}
Property changes on: lucene/contrib/facet/src/test/org/apache/lucene/facet/search/SearcherTaxoManagerTest.java
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/contrib/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java
===================================================================
--- lucene/contrib/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java (revision 1326275)
+++ lucene/contrib/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyWriter.java (working copy)
@@ -200,5 +200,29 @@
dir.close();
}
+
+ @Test
+ public void testTimestamps() throws Exception {
+ // ensures that an application cannot override the create/update time stamps.
+ Directory dir = newDirectory();
+
+ DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, new NoOpCache());
+ taxoWriter.addCategory(new CategoryPath("a"));
+ HashMap<String, String> commitUserData = new HashMap<String, String>();
+ // set these properties -- they should not be committed like that !
+ commitUserData.put(DirectoryTaxonomyWriter.INDEX_CREATE_TIME, "create");
+ commitUserData.put(DirectoryTaxonomyWriter.INDEX_UPDATE_TIME, "update");
+ taxoWriter.commit(commitUserData);
+ taxoWriter.close();
+
+ DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(dir);
+ Map<String, String> commitData = taxoReader.getCommitUserData();
+ taxoReader.close();
+ dir.close();
+
+ // parse the timestamp properties -- they should be parseable (i.e. not overridden above).
+ Long.parseLong(commitData.get(DirectoryTaxonomyWriter.INDEX_CREATE_TIME));
+ Long.parseLong(commitData.get(DirectoryTaxonomyWriter.INDEX_UPDATE_TIME));
+ }
}
Index: lucene/contrib/facet/src/java/org/apache/lucene/facet/search/SearcherTaxoManager.java
===================================================================
--- lucene/contrib/facet/src/java/org/apache/lucene/facet/search/SearcherTaxoManager.java (revision 0)
+++ lucene/contrib/facet/src/java/org/apache/lucene/facet/search/SearcherTaxoManager.java (working copy)
@@ -0,0 +1,286 @@
+package org.apache.lucene.facet.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.lucene.facet.search.SearcherTaxoManager.SearcherTaxoPair;
+import org.apache.lucene.facet.taxonomy.InconsistentTaxonomyException;
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.ReferenceManager;
+import org.apache.lucene.search.SearcherFactory;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+
+public class SearcherTaxoManager extends ReferenceManager<SearcherTaxoPair> {
+
+ public static class SearcherTaxoPair {
+ public final IndexSearcher searcher;
+ public final TaxonomyReader taxoReader;
+
+ public SearcherTaxoPair(IndexSearcher is, TaxonomyReader tr) {
+ searcher = is;
+ taxoReader = tr;
+ }
+ }
+
+ private static final Logger logger = Logger.getLogger(SearcherTaxoManager.class.getName());
+
+ /** Holds the create and last update times of a taxonomy index. */
+ public static final class TaxonomyTimestamp {
+
+ /**
+ * The property name that exists in the commit data of the search index, and
+ * denotes the taxonomy index update time.
+ */
+ public static final String TAXO_UPDATE_TIME = "TAXONOMY_INDEX_UPDATE_TIME";
+
+ /**
+ * The property name that exists in the commit data of the search index, and
+ * denotes the taxonomy index creation time.
+ */
+ public static final String TAXO_CREATE_TIME = "TAXONOMY_INDEX_CREATE_TIME";
+
+ public final long createTime;
+ public final long lastUpdateTime;
+
+ /** Creates a {@link TaxonomyTimestamp} from the commit data of a taxonomy index. */
+ public static TaxonomyTimestamp fromTaxonomyCommitData(Map<String, String> commitData) {
+ long createTime = -1, updateTime = -1;
+ if (commitData != null) {
+ String createTimeProp = commitData.get(DirectoryTaxonomyWriter.INDEX_CREATE_TIME);
+ if (createTimeProp != null) {
+ createTime = Long.parseLong(createTimeProp);
+ }
+
+ String updateTimeProp = commitData.get(DirectoryTaxonomyWriter.INDEX_UPDATE_TIME);
+ if (updateTimeProp != null) {
+ updateTime = Long.parseLong(updateTimeProp);
+ }
+ }
+ return new TaxonomyTimestamp(createTime, updateTime);
+ }
+
+ /** Creates a {@link TaxonomyTimestamp} from the commit data of a search index. */
+ public static TaxonomyTimestamp fromSearchCommitData(Map<String, String> commitData) {
+ long createTime = -1, updateTime = -1;
+ if (commitData != null) {
+ String createTimeProp = commitData.get(TAXO_CREATE_TIME);
+ if (createTimeProp != null) {
+ createTime = Long.parseLong(createTimeProp);
+ }
+
+ String updateTimeProp = commitData.get(TAXO_UPDATE_TIME);
+ if (updateTimeProp != null) {
+ updateTime = Long.parseLong(updateTimeProp);
+ }
+ }
+ return new TaxonomyTimestamp(createTime, updateTime);
+ }
+
+ public TaxonomyTimestamp(long createTime, long lastUpdateTime) {
+ this.createTime = createTime;
+ this.lastUpdateTime = lastUpdateTime;
+ }
+
+ public Map<String, String> toSearchCommitData() {
+ return new HashMap<String, String>() {{
+ put(TAXO_CREATE_TIME, Long.toString(createTime));
+ put(TAXO_UPDATE_TIME, Long.toString(lastUpdateTime));
+ }};
+ }
+
+ @Override
+ public String toString() {
+ return "createTime=" + createTime + " lastUpdateTime=" + lastUpdateTime;
+ }
+
+ }
+
+ private final SearcherFactory searcherFactory;
+ private final Directory taxoDir;
+
+ public SearcherTaxoManager(Directory indexDir, Directory taxoDir,
+ SearcherFactory searcherFactory) throws IOException {
+ if (indexDir == null || taxoDir == null) {
+ throw new IllegalArgumentException("indexReader, taxoReader and taxoFactory cannot be null !");
+ }
+
+ IndexReader indexReader = null;
+ TaxonomyReader taxoReader = null;
+ boolean success = false;
+ try {
+ indexReader = IndexReader.open(taxoDir);
+ taxoReader = new DirectoryTaxonomyReader(taxoDir);
+
+ // validate that the IndexReader and TaxoReader are in sync
+ TaxonomyTimestamp searchTaxoToken = getTimestampToken(indexReader);
+ TaxonomyTimestamp taxoIndexToken = getTimestampToken(taxoReader);
+ if (searchTaxoToken.createTime != taxoIndexToken.createTime
+ || searchTaxoToken.lastUpdateTime > taxoIndexToken.lastUpdateTime) {
+ String msg = "Search and Taxonomy indexes times do not match: searchIndex=["
+ + searchTaxoToken + "]; taxoIndex=[" + taxoIndexToken + "]";
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.finest(msg);
+ }
+ throw new IOException(msg);
+ }
+
+ if (searcherFactory == null) {
+ searcherFactory = new SearcherFactory();
+ }
+
+ this.searcherFactory = searcherFactory;
+ current = new SearcherTaxoPair(searcherFactory.newSearcher(indexReader), taxoReader);
+ this.taxoDir = taxoDir;
+ success = true;
+ } finally {
+ if (!success) {
+ IOUtils.closeWhileHandlingException(indexReader, taxoReader);
+ }
+ }
+ }
+
+ @Override
+ protected void decRef(SearcherTaxoPair reference) throws IOException {
+ boolean success1 = false, success2 = false;
+ try {
+ reference.searcher.getIndexReader().decRef();
+ success1 = true;
+ reference.taxoReader.decRef();
+ success2 = true;
+ } finally {
+ if (!success1) {
+ reference.searcher.getIndexReader().incRef();
+ }
+ if (!success2) {
+ reference.taxoReader.incRef();
+ }
+ }
+ }
+
+ @Override
+ protected boolean tryIncRef(SearcherTaxoPair reference) {
+ if (reference.searcher.getIndexReader().tryIncRef()) {
+ reference.taxoReader.incRef();
+ return true;
+ }
+ return false;
+ }
+
+ /** Returns the taxonomy index create/update timestamps that are registered on the search index. */
+ private TaxonomyTimestamp getTimestampToken(IndexReader reader) throws IOException {
+ return TaxonomyTimestamp.fromSearchCommitData(reader.getIndexCommit().getUserData());
+ }
+
+ /** Returns the taxonomy index create/update timestamps that are registered on the taxonomy index. */
+ private TaxonomyTimestamp getTimestampToken(TaxonomyReader taxoReader) throws IOException {
+ return TaxonomyTimestamp.fromTaxonomyCommitData(taxoReader.getCommitUserData());
+ }
+
+ /**
+ * Returns a new {@link SearcherTaxoPair} if the search and taxonomy index are
+ * in sync, otherwise returns {@code null}.
+ */
+ private SearcherTaxoPair getNewPair(TaxonomyReader taxoReader,
+ IndexReader reader, TaxonomyTimestamp readerTaxoToken,
+ TaxonomyTimestamp taxoReaderToken) throws IOException {
+ SearcherTaxoPair pair = null;
+ try {
+ // search and taxonomy indexes are in the same epoch. Refresh taxoReader
+ // and return a new pair if appropriate
+ taxoReader.refresh();
+
+ // The taxonomy wasn't recreated - make sure that its lastUpdate is >=
+ // indexReader's lastUpdate (otherwise it means that the indexReader
+ // contains categories that are not yet visible to the taxonomy reader).
+ taxoReaderToken = getTimestampToken(taxoReader);
+ if (readerTaxoToken.lastUpdateTime <= taxoReaderToken.lastUpdateTime) {
+ pair = new SearcherTaxoPair(searcherFactory.newSearcher(reader), taxoReader);
+ }
+ } catch (InconsistentTaxonomyException e) {
+ // The taxonomy cannot be refreshed -- could be that someone just
+ // committed a 'recreate' on the taxonomy - we cannot return a new pair.
+ }
+ return pair;
+ }
+
+ @Override
+ protected SearcherTaxoPair refreshIfNeeded(SearcherTaxoPair referenceToRefresh) throws IOException {
+ IndexReader newIndexReader = IndexReader.openIfChanged(referenceToRefresh.searcher.getIndexReader());
+ if (newIndexReader == null) {
+ // search index was not updated, don't update the pair (even if the
+ // taxonomy index has been updated)
+ return null;
+ }
+
+ SearcherTaxoPair newPair = null;
+ TaxonomyTimestamp newReaderTaxoToken = null;
+ TaxonomyTimestamp taxoReaderToken = null;
+ TaxonomyReader newTaxoReader = null;
+ boolean success = false;
+ try {
+ newReaderTaxoToken = getTimestampToken(newIndexReader);
+ taxoReaderToken = getTimestampToken(referenceToRefresh.taxoReader);
+ if (newReaderTaxoToken.createTime == taxoReaderToken.createTime) {
+ newPair = getNewPair(referenceToRefresh.taxoReader,
+ newIndexReader, newReaderTaxoToken, taxoReaderToken);
+ if (newPair != null) {
+ // need to incRef() because we return a new instance of
+ // SearcherTaxoPair, but share the taxoReader instance, and
+ // ReferenceManager will soon 'release' us, so need to adjust the
+ // reference count accordingly.
+ newPair.taxoReader.incRef();
+ }
+ } else {
+ // the taxonomy index and search index's createTime differs. Open a new
+ // TaxonomyReader and try to get a matching pair with it.
+ newTaxoReader = new DirectoryTaxonomyReader(taxoDir);
+ taxoReaderToken = getTimestampToken(newTaxoReader);
+ if (newReaderTaxoToken.createTime == taxoReaderToken.createTime) {
+ newPair = getNewPair(newTaxoReader, newIndexReader, newReaderTaxoToken, taxoReaderToken);
+ }
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ IOUtils.closeWhileHandlingException(newIndexReader, newTaxoReader);
+ }
+ }
+
+ if (newPair == null) {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.finest("Search and Taxonomy indexes times do not match: searchIndex=["
+ + newReaderTaxoToken + "]; taxoIndex=[" + taxoReaderToken + "]");
+ }
+ // the taxonomy and search indexes do not match, close what was opened
+ IOUtils.close(newTaxoReader, newIndexReader);
+ }
+
+ return newPair;
+ }
+
+}
Property changes on: lucene/contrib/facet/src/java/org/apache/lucene/facet/search/SearcherTaxoManager.java
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/contrib/facet/src/java/org/apache/lucene/facet/search/TaxonomyReaderFactory.java
===================================================================
--- lucene/contrib/facet/src/java/org/apache/lucene/facet/search/TaxonomyReaderFactory.java (revision 0)
+++ lucene/contrib/facet/src/java/org/apache/lucene/facet/search/TaxonomyReaderFactory.java (working copy)
@@ -0,0 +1,56 @@
+package org.apache.lucene.facet.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
+import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Factory class used by {@link SearcherTaxoManager} to create new
+ * TaxonomyReaders. {@link DirectoryTaxonomyReaderFactory} returns
+ * {@link DirectoryTaxonomyReader}s on a given {@link Directory}.
+ *
+ * @lucene.experimental
+ */
+public interface TaxonomyReaderFactory {
+
+ /**
+ * A {@link TaxonomyReaderFactory} which returns
+ * {@link DirectoryTaxonomyReader}s on the given {@link Directory}.
+ */
+ public static class DirectoryTaxonomyReaderFactory implements TaxonomyReaderFactory {
+
+ private final Directory taxoDir;
+
+ public DirectoryTaxonomyReaderFactory(Directory taxoDir) {
+ this.taxoDir = taxoDir;
+ }
+
+ public TaxonomyReader newTaxonomyReader() throws IOException {
+ return new DirectoryTaxonomyReader(taxoDir);
+ }
+
+ }
+
+ /** Returns a new TaxonomyReader. */
+ public TaxonomyReader newTaxonomyReader() throws IOException;
+
+}
Property changes on: lucene/contrib/facet/src/java/org/apache/lucene/facet/search/TaxonomyReaderFactory.java
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/contrib/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java
===================================================================
--- lucene/contrib/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java (revision 1326275)
+++ lucene/contrib/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyWriter.java (working copy)
@@ -83,14 +83,21 @@
public class DirectoryTaxonomyWriter implements TaxonomyWriter {
/**
- * Property name of user commit data that contains the creation time of a
- * taxonomy index.
+ * Property name of user commit data that contains the creation time (in nano
+ * seconds) of a taxonomy index.
* <p>
* Applications should not use this property in their commit data because it
* will be overridden by this taxonomy writer.
*/
public static final String INDEX_CREATE_TIME = "index.create.time";
+ /**
+ * Property name of user commit data that contains the update time (in nano
+ * seconds) of a taxonomy index. Set to the time that {@link #commit} is
+ * called.
+ */
+ public static final String INDEX_UPDATE_TIME = "index.update.time";
+
private IndexWriter indexWriter;
private int nextID;
private char delimiter = Consts.DEFAULT_DELIMITER;
@@ -649,6 +656,9 @@
if (userData != null) {
m.putAll(userData);
}
+ // application is not allowed to override these properties, therefore set
+ // them last.
+ m.put(INDEX_UPDATE_TIME, Long.toString(System.nanoTime()));
if (createTime != null) {
m.put(INDEX_CREATE_TIME, createTime);
}