diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexClockCache.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexClockCache.java index 4c19ae4589591..7f521acbc409c 100644 --- a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexClockCache.java +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexClockCache.java @@ -35,11 +35,11 @@ public void elementCleaned( IndexReference searcher ) { try { - searcher.dispose( true ); + searcher.dispose(); } catch ( IOException e ) { throw new RuntimeException( e ); } } -} \ No newline at end of file +} diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReference.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReference.java index a2f76bcf039ca..781639e4ea077 100644 --- a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReference.java +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReference.java @@ -1,4 +1,4 @@ -/* + /* * Copyright (c) 2002-2016 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * @@ -19,21 +19,18 @@ */ package org.neo4j.index.impl.lucene.legacy; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.IndexSearcher; -class IndexReference +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +abstract class IndexReference { private final IndexIdentifier identifier; - private final IndexWriter writer; private final IndexSearcher searcher; private final AtomicInteger refCount = new AtomicInteger( 0 ); private boolean searcherIsClosed; - private boolean writerIsClosed; /** * We need this because we only want to close the reader/searcher if @@ -43,23 +40,23 @@ class IndexReference */ private volatile boolean detached; - private final AtomicBoolean stale = new AtomicBoolean(); - - public IndexReference( IndexIdentifier identifier, IndexSearcher searcher, IndexWriter writer ) + public IndexReference( IndexIdentifier identifier, IndexSearcher searcher ) { this.identifier = identifier; this.searcher = searcher; - this.writer = writer; } - public IndexSearcher getSearcher() - { - return this.searcher; - } + abstract IndexWriter getWriter(); - public IndexWriter getWriter() + abstract void dispose() throws IOException; + + abstract boolean checkAndClearStale(); + + abstract void setStale(); + + public IndexSearcher getSearcher() { - return writer; + return searcher; } public IndexIdentifier getIdentifier() @@ -72,26 +69,20 @@ void incRef() this.refCount.incrementAndGet(); } - public synchronized void dispose( boolean writerAlso ) throws IOException + void disposeSearcher() throws IOException { if ( !searcherIsClosed ) { searcher.getIndexReader().close(); searcherIsClosed = true; } - - if ( writerAlso && !writerIsClosed ) - { - writer.close(); - writerIsClosed = true; - } } - public /*synchronized externally*/ void detachOrClose() throws IOException + void detachOrClose() throws IOException { if ( this.refCount.get() == 0 ) { - dispose( false ); + disposeSearcher(); } else { @@ -99,7 +90,7 @@ public synchronized void dispose( boolean writerAlso ) throws IOException } } - synchronized boolean close() + public synchronized boolean close() { try { @@ -111,7 +102,7 @@ synchronized boolean close() boolean reallyClosed = false; if ( this.refCount.decrementAndGet() <= 0 && this.detached ) { - dispose( false ); + disposeSearcher(); reallyClosed = true; } return reallyClosed; @@ -122,18 +113,13 @@ synchronized boolean close() } } - /*synchronized externally*/ boolean isClosed() + public boolean isClosed() { return searcherIsClosed; } - /*synchronized externally*/ boolean checkAndClearStale() - { - return stale.compareAndSet( true, false ); - } - - public synchronized void setStale() + boolean isDetached() { - stale.set( true ); + return detached; } } diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReferenceFactory.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReferenceFactory.java new file mode 100644 index 0000000000000..aa92b7c238513 --- /dev/null +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/IndexReferenceFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.store.Directory; + +import java.io.File; +import java.io.IOException; + +/** + * Factory that build appropriate (read only or writable) {@link IndexReference} for provided {@link IndexIdentifier} + * or refresh previously constructed instance. + */ +abstract class IndexReferenceFactory +{ + private final File baseStorePath; + private final IndexTypeCache typeCache; + private final LuceneDataSource.LuceneFilesystemFacade filesystemFacade; + + IndexReferenceFactory( LuceneDataSource.LuceneFilesystemFacade filesystemFacade, File baseStorePath, + IndexTypeCache typeCache ) + { + this.filesystemFacade = filesystemFacade; + this.baseStorePath = baseStorePath; + this.typeCache = typeCache; + } + + /** + * Create new {@link IndexReference} for provided {@link IndexIdentifier}. + * @param indexIdentifier index identifier to build index for. + * @return newly create {@link IndexReference} + * + * @throws IOException in case of exception during accessing lucene reader/writer. + */ + abstract IndexReference createIndexReference( IndexIdentifier indexIdentifier ) throws IOException; + + /** + * Refresh previously constructed indexReference. + * @param indexReference index reference to refresh + * @return refreshed index reference + */ + abstract IndexReference refresh( IndexReference indexReference ); + + Directory getIndexDirectory( IndexIdentifier identifier ) throws IOException + { + return filesystemFacade.getDirectory( baseStorePath, identifier ); + } + + IndexSearcher newIndexSearcher( IndexIdentifier identifier, IndexReader reader ) + { + IndexSearcher searcher = new IndexSearcher( reader ); + IndexType type = getType( identifier ); + if ( type.getSimilarity() != null ) + { + searcher.setSimilarity( type.getSimilarity() ); + } + return searcher; + } + + IndexType getType( IndexIdentifier identifier ) + { + return typeCache.getIndexType( identifier, false ); + } +} + diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSource.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSource.java index 98604472e679c..aba3f4dc164e0 100644 --- a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSource.java +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSource.java @@ -19,15 +19,6 @@ */ package org.neo4j.index.impl.lucene.legacy; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; - import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.Tokenizer; @@ -36,22 +27,26 @@ import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.analysis.core.WhitespaceTokenizer; import org.apache.lucene.document.Document; -import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexCommit; -import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.SnapshotDeletionPolicy; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopFieldCollector; -import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.ResourceIterator; @@ -61,6 +56,7 @@ import org.neo4j.helpers.collection.Pair; import org.neo4j.helpers.collection.PrefetchingResourceIterator; import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.fs.FileUtils; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.factory.GraphDatabaseFacadeFactory; import org.neo4j.kernel.impl.index.IndexConfigStore; @@ -73,10 +69,6 @@ */ public class LuceneDataSource extends LifecycleAdapter { - private final File storeDir; - private final Config config; - private final FileSystemAbstraction fileSystemAbstraction; - public static abstract class Configuration { public static final Setting lucene_searcher_cache_size = GraphDatabaseSettings.lucene_searcher_cache_size; @@ -102,15 +94,21 @@ public String toString() return "LOWER_CASE_WHITESPACE_ANALYZER"; } }; + public static final Analyzer WHITESPACE_ANALYZER = new WhitespaceAnalyzer(); public static final Analyzer KEYWORD_ANALYZER = new KeywordAnalyzer(); + private final File storeDir; + private final Config config; + private final FileSystemAbstraction fileSystemAbstraction; private IndexClockCache indexSearchers; private File baseStorePath; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); final IndexConfigStore indexStore; private IndexTypeCache typeCache; + private boolean readOnly; private boolean closed; private LuceneFilesystemFacade filesystemFacade; + private IndexReferenceFactory indexReferenceFactory; /** * Constructs this data source. @@ -129,11 +127,15 @@ public void init() { this.filesystemFacade = config.get( Configuration.ephemeral ) ? LuceneFilesystemFacade.MEMORY : LuceneFilesystemFacade.FS; + readOnly = config.get( GraphDatabaseSettings.read_only ); indexSearchers = new IndexClockCache( config.get( Configuration.lucene_searcher_cache_size ) ); this.baseStorePath = this.filesystemFacade.ensureDirectoryExists( fileSystemAbstraction, getLuceneIndexStoreDirectory( storeDir ) ); this.filesystemFacade.cleanWriteLocks( baseStorePath ); this.typeCache = new IndexTypeCache( indexStore ); + this.indexReferenceFactory = readOnly ? + new ReadOnlyIndexReferenceFactory( filesystemFacade, baseStorePath, typeCache ) : + new WritableIndexReferenceFactory( filesystemFacade, baseStorePath, typeCache ); closed = false; } @@ -159,7 +161,7 @@ public void shutdown() throws IOException closed = true; for ( IndexReference searcher : indexSearchers.values() ) { - searcher.dispose( true ); + searcher.dispose(); } indexSearchers.clear(); } @@ -173,6 +175,10 @@ private synchronized IndexReference[] getAllIndexes() void force() { + if ( readOnly ) + { + return; + } for ( IndexReference index : getAllIndexes() ) { try @@ -206,41 +212,6 @@ void releaseWriteLock() lock.writeLock().unlock(); } - /** - * If nothing has changed underneath (since the searcher was last created - * or refreshed) {@code searcher} is returned. But if something has changed a - * refreshed searcher is returned. It makes use if the - * {@link DirectoryReader#openIfChanged(DirectoryReader, IndexWriter, boolean)} which faster than opening an index - * from - * scratch. - * - * @param searcher the {@link IndexSearcher} to refresh. - * @return a refreshed version of the searcher or, if nothing has changed, - * {@code null}. - * @throws RuntimeException if there's a problem with the index. - */ - private IndexReference refreshSearcher( IndexReference searcher ) - { - try - { - // TODO: this cast should always succeed, maybe check nonetheless? - DirectoryReader reader = (DirectoryReader) searcher.getSearcher().getIndexReader(); - IndexWriter writer = searcher.getWriter(); - IndexReader reopened = DirectoryReader.openIfChanged( reader, writer ); - if ( reopened != null ) - { - IndexSearcher newSearcher = newIndexSearcher( searcher.getIdentifier(), reopened ); - searcher.detachOrClose(); - return new IndexReference( searcher.getIdentifier(), newSearcher, writer ); - } - return searcher; - } - catch ( IOException e ) - { - throw new RuntimeException( e ); - } - } - static File getFileDirectory( File storeDir, IndexEntityType type ) { File path = new File( storeDir, "lucene" ); @@ -301,24 +272,24 @@ synchronized IndexReference syncGetIndexSearcher( IndexIdentifier identifier ) { try { - IndexReference searcher = indexSearchers.get( identifier ); - if ( searcher == null ) + IndexReference indexReference = indexSearchers.get( identifier ); + if ( indexReference == null ) { - IndexWriter writer = newIndexWriter( identifier ); - IndexReader reader = DirectoryReader.open( writer ); - IndexSearcher indexSearcher = newIndexSearcher( identifier, reader ); - searcher = new IndexReference( identifier, indexSearcher, writer ); - indexSearchers.put( identifier, searcher ); + indexReference = indexReferenceFactory.createIndexReference( identifier ); + indexSearchers.put( identifier, indexReference ); } else { - synchronized ( searcher ) + if ( !readOnly ) { - searcher = refreshSearcherIfNeeded( searcher ); + synchronized ( indexReference ) + { + indexReference = refreshSearcherIfNeeded( indexReference ); + } } } - searcher.incRef(); - return searcher; + indexReference.incRef(); + return indexReference; } catch ( IOException e ) { @@ -326,22 +297,11 @@ synchronized IndexReference syncGetIndexSearcher( IndexIdentifier identifier ) } } - private IndexSearcher newIndexSearcher( IndexIdentifier identifier, IndexReader reader ) - { - IndexSearcher searcher = new IndexSearcher( reader ); - IndexType type = getType( identifier, false ); - if ( type.getSimilarity() != null ) - { - searcher.setSimilarity( type.getSimilarity() ); - } - return searcher; - } - private IndexReference refreshSearcherIfNeeded( IndexReference searcher ) { if ( searcher.checkAndClearStale() ) { - searcher = refreshSearcher( searcher ); + searcher = indexReferenceFactory.refresh( searcher ); if ( searcher != null ) { indexSearchers.put( searcher.getIdentifier(), searcher ); @@ -359,10 +319,14 @@ void invalidateIndexSearcher( IndexIdentifier identifier ) } } - void deleteIndex( IndexIdentifier identifier, boolean recovery ) + void deleteIndex( IndexIdentifier identifier, boolean recovery ) throws IOException { + if ( readOnly ) + { + throw new IllegalStateException( "Index deletion in read only mode is not supported." ); + } closeIndex( identifier ); - deleteFileOrDirectory( getFileDirectory( baseStorePath, identifier ) ); + FileUtils.deleteRecursively( getFileDirectory( baseStorePath, identifier ) ); boolean removeFromIndexStore = !recovery || (indexStore.has( identifier.entityType.entityClass(), identifier.indexName )); if ( removeFromIndexStore ) @@ -372,63 +336,6 @@ void deleteIndex( IndexIdentifier identifier, boolean recovery ) typeCache.invalidate( identifier ); } - private static void deleteFileOrDirectory( File file ) - { - if ( file.exists() ) - { - if ( file.isDirectory() ) - { - for ( File child : file.listFiles() ) - { - deleteFileOrDirectory( child ); - } - } - file.delete(); - } - } - - private/*synchronized elsewhere*/IndexWriter newIndexWriter( IndexIdentifier identifier ) - { - assertNotClosed(); - try - { - Directory dir = filesystemFacade.getDirectory( baseStorePath, identifier ); //getDirectory( - // baseStorePath, identifier ); - directoryExists( dir ); - IndexType type = getType( identifier, false ); - IndexWriterConfig writerConfig = new IndexWriterConfig( type.analyzer ); - writerConfig.setIndexDeletionPolicy( new MultipleBackupDeletionPolicy() ); - Similarity similarity = type.getSimilarity(); - if ( similarity != null ) - { - writerConfig.setSimilarity( similarity ); - } - IndexWriter indexWriter = new IndexWriter( dir, writerConfig ); - // TODO We should tamper with this value and see how it affects the - // general performance. Lucene docs says rather <10 for mixed - // reads/writes - // writer.setMergeFactor( 8 ); - return indexWriter; - } - catch ( IOException e ) - { - throw new RuntimeException( e ); - } - } - - private boolean directoryExists( Directory dir ) - { - try - { - String[] files = dir.listAll(); - return files != null && files.length > 0; - } - catch ( IOException e ) - { - return false; - } - } - static Document findDocument( IndexType type, IndexSearcher searcher, long entityId ) { try @@ -467,7 +374,7 @@ private synchronized void closeIndex( IndexIdentifier identifier ) IndexReference searcher = indexSearchers.remove( identifier ); if ( searcher != null ) { - searcher.dispose( true ); + searcher.dispose(); } } catch ( IOException e ) @@ -567,7 +474,7 @@ private void makeSureAllIndexesAreInstantiated() } } - private enum LuceneFilesystemFacade + enum LuceneFilesystemFacade { FS { diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReference.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReference.java new file mode 100644 index 0000000000000..aa720e577d29c --- /dev/null +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReference.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.search.IndexSearcher; + +import java.io.IOException; + +public class ReadOnlyIndexReference extends IndexReference +{ + + ReadOnlyIndexReference( IndexIdentifier identifier, IndexSearcher searcher ) + { + super(identifier, searcher); + } + + @Override + public IndexWriter getWriter() + { + throw new UnsupportedOperationException( "Read only indexes do not have index writers." ); + } + + @Override + public synchronized void dispose() throws IOException + { + disposeSearcher(); + } + + @Override + public boolean checkAndClearStale() + { + return false; + } + + @Override + public void setStale() + { + throw new UnsupportedOperationException("Read only indexes can't be marked as stale."); + } + +} diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactory.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactory.java new file mode 100644 index 0000000000000..ae8e3bb5918fc --- /dev/null +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.IndexSearcher; + +import java.io.File; +import java.io.IOException; + +public class ReadOnlyIndexReferenceFactory extends IndexReferenceFactory +{ + public ReadOnlyIndexReferenceFactory( LuceneDataSource.LuceneFilesystemFacade filesystemFacade, File baseStorePath, + IndexTypeCache typeCache ) + { + super( filesystemFacade, baseStorePath, typeCache ); + } + + @Override + IndexReference createIndexReference( IndexIdentifier identifier ) throws IOException + { + IndexReader reader = DirectoryReader.open( getIndexDirectory( identifier ) ); + IndexSearcher indexSearcher = newIndexSearcher( identifier, reader ); + return new ReadOnlyIndexReference( identifier, indexSearcher ); + } + + @Override + IndexReference refresh( IndexReference indexReference ) + { + return indexReference; + } +} diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReference.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReference.java new file mode 100644 index 0000000000000..453ffa9a62930 --- /dev/null +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReference.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.search.IndexSearcher; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +class WritableIndexReference extends IndexReference +{ + private final IndexWriter writer; + private boolean writerIsClosed; + private final AtomicBoolean stale = new AtomicBoolean(); + + WritableIndexReference( IndexIdentifier identifier, IndexSearcher searcher, IndexWriter writer ) + { + super(identifier, searcher ); + this.writer = writer; + } + + @Override + public IndexWriter getWriter() + { + return writer; + } + + @Override + public synchronized void dispose() throws IOException + { + disposeSearcher(); + disposeWriter(); + } + + @Override + public boolean checkAndClearStale() + { + return stale.compareAndSet( true, false ); + } + + @Override + public void setStale() + { + stale.set( true ); + } + + private void disposeWriter() throws IOException + { + if ( !writerIsClosed ) + { + writer.close(); + writerIsClosed = true; + } + } + + boolean isWriterClosed() + { + return writerIsClosed; + } +} diff --git a/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactory.java b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactory.java new file mode 100644 index 0000000000000..a386797f368d1 --- /dev/null +++ b/community/lucene-index/src/main/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.similarities.Similarity; +import org.apache.lucene.store.Directory; + +import java.io.File; +import java.io.IOException; + +class WritableIndexReferenceFactory extends IndexReferenceFactory +{ + WritableIndexReferenceFactory( LuceneDataSource.LuceneFilesystemFacade filesystemFacade, File baseStorePath, + IndexTypeCache typeCache ) + { + super( filesystemFacade, baseStorePath, typeCache ); + } + + @Override + IndexReference createIndexReference( IndexIdentifier identifier ) throws IOException + { + IndexWriter writer = newIndexWriter( identifier ); + IndexReader reader = DirectoryReader.open( writer, true ); + IndexSearcher indexSearcher = newIndexSearcher( identifier, reader ); + return new WritableIndexReference( identifier, indexSearcher, writer ); + } + + /** + * If nothing has changed underneath (since the searcher was last created + * or refreshed) {@code searcher} is returned. But if something has changed a + * refreshed searcher is returned. It makes use if the + * {@link DirectoryReader#openIfChanged(DirectoryReader, IndexWriter, boolean)} which faster than opening an index + * from + * scratch. + * + * @param indexReference the {@link IndexReference} to refresh. + * @return a refreshed version of the searcher or, if nothing has changed, + * {@code null}. + * @throws RuntimeException if there's a problem with the index. + */ + @Override + IndexReference refresh( IndexReference indexReference ) + { + try + { + DirectoryReader reader = (DirectoryReader) indexReference.getSearcher().getIndexReader(); + IndexWriter writer = indexReference.getWriter(); + IndexReader reopened = DirectoryReader.openIfChanged( reader, writer ); + if ( reopened != null ) + { + IndexSearcher newSearcher = newIndexSearcher( indexReference.getIdentifier(), reopened ); + indexReference.detachOrClose(); + return new WritableIndexReference( indexReference.getIdentifier(), newSearcher, writer ); + } + return indexReference; + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } + + private IndexWriter newIndexWriter( IndexIdentifier identifier ) + { + try + { + Directory indexDirectory = getIndexDirectory( identifier ); + IndexType type = getType( identifier ); + IndexWriterConfig writerConfig = new IndexWriterConfig( type.analyzer ); + writerConfig.setIndexDeletionPolicy( new MultipleBackupDeletionPolicy() ); + Similarity similarity = type.getSimilarity(); + if ( similarity != null ) + { + writerConfig.setSimilarity( similarity ); + } + return new IndexWriter( indexDirectory, writerConfig ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } +} diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/CloseTrackingIndexReader.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/CloseTrackingIndexReader.java new file mode 100644 index 0000000000000..fc3b478f829ff --- /dev/null +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/CloseTrackingIndexReader.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.CompositeReader; +import org.apache.lucene.index.Fields; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.StoredFieldVisitor; +import org.apache.lucene.index.Term; + +import java.io.IOException; +import java.util.List; + + +public class CloseTrackingIndexReader extends CompositeReader +{ + + private boolean closed = false; + + @Override + protected List getSequentialSubReaders() + { + return null; + } + + @Override + public Fields getTermVectors( int docID ) throws IOException + { + return null; + } + + @Override + public int numDocs() + { + return 0; + } + + @Override + public int maxDoc() + { + return 0; + } + + @Override + public void document( int docID, StoredFieldVisitor visitor ) throws IOException + { + + } + + @Override + protected void doClose() throws IOException + { + closed = true; + } + + @Override + public int docFreq( Term term ) throws IOException + { + return 0; + } + + @Override + public long totalTermFreq( Term term ) throws IOException + { + return 0; + } + + @Override + public long getSumDocFreq( String field ) throws IOException + { + return 0; + } + + @Override + public int getDocCount( String field ) throws IOException + { + return 0; + } + + @Override + public long getSumTotalTermFreq( String field ) throws IOException + { + return 0; + } + + public boolean isClosed() + { + return closed; + } +} diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSourceTest.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSourceTest.java index a0f09daa93702..9e796501304d1 100644 --- a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSourceTest.java +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/LuceneDataSourceTest.java @@ -23,8 +23,10 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.RuleChain; +import java.io.IOException; import java.util.Map; import org.neo4j.graphdb.Node; @@ -40,6 +42,7 @@ import org.neo4j.test.TargetDirectory.TestDirectory; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -48,29 +51,78 @@ public class LuceneDataSourceTest { private final LifeRule life = new LifeRule( true ); private final TestDirectory directory = TargetDirectory.testDirForTest( getClass() ); + private final ExpectedException expectedException = ExpectedException.none(); @Rule - public final RuleChain ruleChain = RuleChain.outerRule( directory ) - .around( life ); + public final RuleChain ruleChain = RuleChain.outerRule( directory ).around( life ).around( expectedException ); private IndexConfigStore indexStore; private LuceneDataSource dataSource; @Before - public void setup() + public void setUp() { indexStore = new IndexConfigStore( directory.directory(), new DefaultFileSystemAbstraction() ); addIndex( "foo" ); } - private void addIndex( String name ) + @Test + public void doNotTryToCommitWritersOnForceInReadOnlyMode() throws IOException { - indexStore.set( Node.class, name, MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ) ); + IndexIdentifier indexIdentifier = identifier( "foo" ); + prepareIndexesByIdentifiers( indexIdentifier ); + + Config readOnlyConfig = new Config( readOnlyConfig(), GraphDatabaseSettings.class ); + LuceneDataSource readOnlyDataSource = life.add( new LuceneDataSource( directory.graphDbDir(), readOnlyConfig, + indexStore, new DefaultFileSystemAbstraction() ) ); + assertNotNull( readOnlyDataSource.getIndexSearcher( indexIdentifier ) ); + + readOnlyDataSource.force(); } - private IndexIdentifier identifier( String name ) + @Test + public void notAllowIndexDeletionInReadOnlyMode() throws IOException { - return new IndexIdentifier( IndexEntityType.Node, name ); + IndexIdentifier indexIdentifier = identifier( "foo" ); + prepareIndexesByIdentifiers( indexIdentifier ); + + Config readOnlyConfig = new Config( readOnlyConfig(), GraphDatabaseSettings.class ); + dataSource = life.add( new LuceneDataSource( directory.graphDbDir(), readOnlyConfig, indexStore, new DefaultFileSystemAbstraction() ) ); + expectedException.expect( IllegalStateException.class ); + expectedException.expectMessage("Index deletion in read only mode is not supported."); + dataSource.deleteIndex( indexIdentifier, false ); + } + + @Test + public void useReadOnlyIndexSearcherInReadOnlyMode() throws IOException + { + IndexIdentifier indexIdentifier = identifier( "foo" ); + prepareIndexesByIdentifiers( indexIdentifier ); + + Config readOnlyConfig = new Config( readOnlyConfig(), GraphDatabaseSettings.class ); + dataSource = life.add( new LuceneDataSource( directory.graphDbDir(), readOnlyConfig, indexStore, new DefaultFileSystemAbstraction() ) ); + + IndexReference indexSearcher = dataSource.getIndexSearcher( indexIdentifier ); + assertTrue( "Read only index reference should be used in read only mode.", + ReadOnlyIndexReference.class.isInstance( indexSearcher ) ); + } + + @Test + public void refreshReadOnlyIndexSearcherInReadOnlyMode() throws IOException + { + IndexIdentifier indexIdentifier = identifier( "foo" ); + prepareIndexesByIdentifiers( indexIdentifier ); + + Config readOnlyConfig = new Config( readOnlyConfig(), GraphDatabaseSettings.class ); + dataSource = life.add( new LuceneDataSource( directory.graphDbDir(), readOnlyConfig, indexStore, new DefaultFileSystemAbstraction() ) ); + + IndexReference indexSearcher = dataSource.getIndexSearcher( indexIdentifier ); + IndexReference indexSearcher2 = dataSource.getIndexSearcher( indexIdentifier ); + IndexReference indexSearcher3 = dataSource.getIndexSearcher( indexIdentifier ); + IndexReference indexSearcher4 = dataSource.getIndexSearcher( indexIdentifier ); + assertSame( "Refreshed read only searcher should be the same.", indexSearcher, indexSearcher2 ); + assertSame( "Refreshed read only searcher should be the same.", indexSearcher2, indexSearcher3 ); + assertSame( "Refreshed read only searcher should be the same.", indexSearcher3, indexSearcher4 ); } @Test @@ -180,6 +232,32 @@ public void testRecreatesWriterWhenRequestedAgainAfterCacheEviction() throws Thr private Map config() { - return MapUtil.stringMap( "store_dir", directory.directory().getPath() ); + return MapUtil.stringMap(); + } + + private void prepareIndexesByIdentifiers( IndexIdentifier indexIdentifier ) + { + Config config = new Config( config(), GraphDatabaseSettings.class ); + dataSource = life.add( new LuceneDataSource( directory.graphDbDir(), config, indexStore, new DefaultFileSystemAbstraction() ) ); + dataSource.getIndexSearcher( indexIdentifier ); + dataSource.force(); + } + + private Map readOnlyConfig() + { + Map config = config(); + config.put( GraphDatabaseSettings.read_only.name(), "true" ); + return config; + } + + private void addIndex( String name ) + { + indexStore.set( Node.class, name, MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ) ); + } + + private IndexIdentifier identifier( String name ) + { + return new IndexIdentifier( IndexEntityType.Node, name ); } + } diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactoryTest.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactoryTest.java new file mode 100644 index 0000000000000..059bad7d0f134 --- /dev/null +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceFactoryTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; + +import java.io.File; +import java.io.IOException; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.index.IndexManager; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.io.fs.DefaultFileSystemAbstraction; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.index.IndexConfigStore; +import org.neo4j.kernel.impl.index.IndexEntityType; +import org.neo4j.test.CleanupRule; +import org.neo4j.test.TargetDirectory; + +import static org.junit.Assert.assertSame; + +public class ReadOnlyIndexReferenceFactoryTest +{ + private TargetDirectory.TestDirectory testDirectory = TargetDirectory.testDirForTest( getClass() ); + private ExpectedException expectedException = ExpectedException.none(); + private CleanupRule cleanupRule = new CleanupRule(); + + @Rule + public RuleChain ruleChain = RuleChain.outerRule( cleanupRule ).around( expectedException ).around( testDirectory ); + + private static final String INDEX_NAME = "testIndex"; + private LuceneDataSource.LuceneFilesystemFacade filesystemFacade = LuceneDataSource.LuceneFilesystemFacade.FS; + private IndexIdentifier indexIdentifier = new IndexIdentifier( IndexEntityType.Node, INDEX_NAME ); + private IndexConfigStore indexStore; + + @Before + public void setUp() throws IOException + { + setupIndexInfrastructure(); + } + + @Test + public void createReadOnlyIndexReference() throws Exception + { + ReadOnlyIndexReferenceFactory indexReferenceFactory = getReadOnlyIndexReferenceFactory(); + IndexReference indexReference = indexReferenceFactory.createIndexReference( indexIdentifier ); + cleanupRule.add( indexReference ); + + expectedException.expect( UnsupportedOperationException.class ); + indexReference.getWriter(); + } + + @Test + public void refreshReadOnlyIndexReference() throws IOException + { + ReadOnlyIndexReferenceFactory indexReferenceFactory = getReadOnlyIndexReferenceFactory(); + IndexReference indexReference = indexReferenceFactory.createIndexReference( indexIdentifier ); + cleanupRule.add( indexReference ); + + IndexReference refreshedIndex = indexReferenceFactory.refresh( indexReference ); + assertSame("Refreshed instance should be the same.", indexReference, refreshedIndex); + } + + private void setupIndexInfrastructure() throws IOException + { + DefaultFileSystemAbstraction fileSystemAbstraction = new DefaultFileSystemAbstraction(); + File storeDir = getStoreDir(); + indexStore = new IndexConfigStore( storeDir, fileSystemAbstraction ); + indexStore.set( Node.class, INDEX_NAME, MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ) ); + LuceneDataSource luceneDataSource = new LuceneDataSource( storeDir, new Config( MapUtil.stringMap() ), + indexStore, fileSystemAbstraction ); + try + { + luceneDataSource.init(); + luceneDataSource.getIndexSearcher( indexIdentifier ); + } + finally + { + luceneDataSource.shutdown(); + } + } + + private ReadOnlyIndexReferenceFactory getReadOnlyIndexReferenceFactory() + { + return new ReadOnlyIndexReferenceFactory( filesystemFacade, new File( getStoreDir(), "index"), + new IndexTypeCache( indexStore ) ); + } + + private File getStoreDir() + { + return testDirectory.directory(); + } +} diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceTest.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceTest.java new file mode 100644 index 0000000000000..ee7a00de5bbd9 --- /dev/null +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/ReadOnlyIndexReferenceTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.search.IndexSearcher; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.IOException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ReadOnlyIndexReferenceTest +{ + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private IndexIdentifier identifier = mock( IndexIdentifier.class ); + private IndexSearcher searcher = mock( IndexSearcher.class ); + private CloseTrackingIndexReader reader = new CloseTrackingIndexReader(); + private ReadOnlyIndexReference indexReference = new ReadOnlyIndexReference( identifier, searcher ); + + @Before + public void setUp() + { + when( searcher.getIndexReader() ).thenReturn( reader ); + } + + @Test + public void obtainingWriterIsUnsupported() throws Exception + { + expectedException.expect( UnsupportedOperationException.class ); + expectedException.expectMessage( "Read only indexes do not have index writers." ); + indexReference.getWriter(); + } + + @Test + public void markAsStaleIsUnsupported() + { + expectedException.expect( UnsupportedOperationException.class ); + expectedException.expectMessage( "Read only indexes can't be marked as stale." ); + indexReference.setStale(); + } + + @Test + public void checkAndClearStaleAlwaysFalse() + { + assertFalse( indexReference.checkAndClearStale() ); + } + + @Test + public void disposeClosingSearcherAndMarkAsClosed() throws IOException + { + indexReference.dispose(); + + assertTrue( reader.isClosed() ); + assertTrue( indexReference.isClosed() ); + } + + @Test + public void detachIndexReferenceWhenSomeReferencesExist() throws IOException + { + indexReference.incRef(); + indexReference.detachOrClose(); + + assertTrue( "Should leave index in detached state.", indexReference.isDetached() ); + } + + @Test + public void closeIndexReferenceWhenNoReferenceExist() throws IOException + { + indexReference.detachOrClose(); + + assertFalse( "Should leave index in closed state.", indexReference.isDetached() ); + assertTrue( reader.isClosed() ); + assertTrue( indexReference.isClosed() ); + } + + @Test + public void doNotCloseInstanceWhenSomeReferenceExist() + { + indexReference.incRef(); + assertFalse( indexReference.close() ); + + assertFalse( indexReference.isClosed() ); + } + + @Test + public void closeDetachedIndexReferencedOnlyOnce() throws IOException + { + indexReference.incRef(); + indexReference.detachOrClose(); + + assertTrue( "Should leave index in detached state.", indexReference.isDetached() ); + + assertTrue( indexReference.close() ); + assertTrue( reader.isClosed() ); + assertTrue( indexReference.isClosed() ); + } + + @Test + public void doNotCloseDetachedIndexReferencedMoreThenOnce() throws IOException + { + indexReference.incRef(); + indexReference.incRef(); + indexReference.detachOrClose(); + + assertTrue( "Should leave index in detached state.", indexReference.isDetached() ); + + assertFalse( indexReference.close() ); + } + + @Test + public void doNotCloseReferencedIndex() + { + indexReference.incRef(); + assertFalse( indexReference.close() ); + assertFalse( indexReference.isClosed() ); + } + + @Test + public void closeNotReferencedIndex() + { + assertTrue( indexReference.close() ); + } +} diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactoryTest.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactoryTest.java new file mode 100644 index 0000000000000..7f1c88413b2f7 --- /dev/null +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceFactoryTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + + +import org.apache.lucene.document.Document; +import org.apache.lucene.index.IndexWriter; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.index.IndexManager; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.io.fs.DefaultFileSystemAbstraction; +import org.neo4j.kernel.impl.index.IndexConfigStore; +import org.neo4j.kernel.impl.index.IndexEntityType; +import org.neo4j.test.CleanupRule; +import org.neo4j.test.TargetDirectory; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +public class WritableIndexReferenceFactoryTest +{ + @Rule + public TargetDirectory.TestDirectory testDirectory = TargetDirectory.testDirForTest( getClass() ); + @Rule + public CleanupRule cleanupRule = new CleanupRule(); + + private static final String INDEX_NAME = "testIndex"; + + private LuceneDataSource.LuceneFilesystemFacade filesystemFacade = LuceneDataSource.LuceneFilesystemFacade.FS; + private IndexIdentifier indexIdentifier = new IndexIdentifier( IndexEntityType.Node, INDEX_NAME ); + private IndexConfigStore indexStore; + + @Before + public void setUp() throws IOException + { + setupIndexInfrastructure(); + } + + @Test + public void createWritableIndexReference() throws Exception + { + WritableIndexReferenceFactory indexReferenceFactory = createFactory(); + IndexReference indexReference = createIndexReference( indexReferenceFactory ); + + assertNotNull( "Index should have writer.", indexReference.getWriter() ); + } + + @Test + public void refreshNotChangedWritableIndexReference() throws Exception + { + WritableIndexReferenceFactory indexReferenceFactory = createFactory(); + IndexReference indexReference = createIndexReference( indexReferenceFactory ); + + IndexReference refreshedInstance = indexReferenceFactory.refresh( indexReference ); + assertSame( indexReference, refreshedInstance ); + } + + @Test + public void refreshChangedWritableIndexReference() throws Exception + { + WritableIndexReferenceFactory indexReferenceFactory = createFactory(); + IndexReference indexReference = createIndexReference( indexReferenceFactory ); + + writeSomething( indexReference ); + + IndexReference refreshedIndexReference = indexReferenceFactory.refresh( indexReference ); + cleanupRule.add( refreshedIndexReference ); + + assertNotSame( "Should return new refreshed index reference.", indexReference, refreshedIndexReference ); + } + + private void writeSomething( IndexReference indexReference ) throws IOException + { + IndexWriter writer = indexReference.getWriter(); + writer.addDocument( new Document() ); + writer.commit(); + } + + private IndexReference createIndexReference( WritableIndexReferenceFactory indexReferenceFactory ) throws IOException + { + IndexReference indexReference = indexReferenceFactory.createIndexReference( indexIdentifier ); + cleanupRule.add( indexReference ); + return indexReference; + } + + private WritableIndexReferenceFactory createFactory() + { + return new WritableIndexReferenceFactory( filesystemFacade, new File( getStoreDir(), "index"), + new IndexTypeCache( indexStore ) ); + } + + private void setupIndexInfrastructure() throws IOException + { + DefaultFileSystemAbstraction fileSystemAbstraction = new DefaultFileSystemAbstraction(); + File storeDir = getStoreDir(); + indexStore = new IndexConfigStore( storeDir, fileSystemAbstraction ); + indexStore.set( Node.class, INDEX_NAME, MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ) ); + } + + private File getStoreDir() + { + return testDirectory.directory(); + } + +} diff --git a/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceTest.java b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceTest.java new file mode 100644 index 0000000000000..7f32fa61540d2 --- /dev/null +++ b/community/lucene-index/src/test/java/org/neo4j/index/impl/lucene/legacy/WritableIndexReferenceTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.impl.lucene.legacy; + +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.search.IndexSearcher; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class WritableIndexReferenceTest +{ + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private IndexIdentifier identifier = mock( IndexIdentifier.class ); + private IndexSearcher searcher = mock( IndexSearcher.class ); + private IndexWriter indexWriter = mock( IndexWriter.class ); + private CloseTrackingIndexReader reader = new CloseTrackingIndexReader(); + private WritableIndexReference indexReference = new WritableIndexReference( identifier, searcher, indexWriter); + + @Before + public void setUp() + { + when( searcher.getIndexReader() ).thenReturn( reader ); + } + + + @Test + public void useProvidedWriterAsIndexWriter() throws Exception + { + assertSame( indexWriter, indexReference.getWriter() ); + } + + @Test + public void stalingWritableIndex() throws Exception + { + assertFalse( "Index is not stale by default.", indexReference.checkAndClearStale() ); + indexReference.setStale(); + assertTrue( "We should be able to reset stale index state.", indexReference.checkAndClearStale() ); + assertFalse( "Index is not stale anymore.", indexReference.checkAndClearStale() ); + + } + + @Test + public void disposeWritableIndex() throws Exception + { + indexReference.dispose(); + assertTrue( "Reader should be closed.", reader.isClosed() ); + assertTrue( "Writer should be closed.", indexReference.isWriterClosed() ); + } + +}