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 extends IndexReader> 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() );
+ }
+
+}