/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.test.reader.functionality;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
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.LeafReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.hibernate.search.indexes.impl.SharingBufferReaderProvider;
import org.hibernate.search.indexes.spi.DirectoryBasedIndexManager;
import org.hibernate.search.spi.BuildContext;
import org.hibernate.search.store.DirectoryProvider;
import org.hibernate.search.store.impl.RAMDirectoryProvider;
import org.hibernate.search.test.util.HibernateManualConfiguration;
import org.hibernate.search.testsupport.setup.BuildContextForTest;
/**
* Testable extension of SharingBufferReaderProvider to make sure IndexReaders
* are only opened when needed, and always correctly closed.
*
* @author Sanne Grinovero
* @see SharingBufferIndexProviderTest
*/
public class ExtendedSharingBufferReaderProvider extends SharingBufferReaderProvider {
private static final int NUM_DIRECTORY_PROVIDERS = 3;
private final Vector<MockIndexReader> createdReadersHistory = new Vector<MockIndexReader>( 500 );
final Map<Directory, TestManipulatorPerDP> manipulators = new ConcurrentHashMap<Directory, TestManipulatorPerDP>();
private final RAMDirectoryProvider[] directories = new RAMDirectoryProvider[NUM_DIRECTORY_PROVIDERS];
private final AtomicInteger currentDirectoryIndex = new AtomicInteger();
private volatile RAMDirectoryProvider currentDirectory;
public ExtendedSharingBufferReaderProvider() {
for ( int i = 0; i < NUM_DIRECTORY_PROVIDERS; i++ ) {
TestManipulatorPerDP tm = new TestManipulatorPerDP( i );
manipulators.put( tm.dp.getDirectory(), tm );
directories[i] = tm.dp;
}
currentDirectory = directories[0];
}
/**
* Contains mutable state related to a specific index
*/
public static class TestManipulatorPerDP {
private final AtomicBoolean isIndexReaderCurrent = new AtomicBoolean( false );//starts at true, see MockIndexReader constructor
private final AtomicBoolean isReaderCreated = new AtomicBoolean( false );
private final RAMDirectoryProvider dp = new RAMDirectoryProvider();
TestManipulatorPerDP(int seed) {
dp.initialize( String.valueOf( seed ), new Properties(), new BuildContextForTest( new HibernateManualConfiguration() ) );
dp.start( null );
}
public void setIndexChanged() {
isIndexReaderCurrent.set( false );
}
}
/**
* Make the last IndexReader opened on the current Directory dirty
*/
public void currentDPWasWritten() {
for ( TestManipulatorPerDP manipulator : manipulators.values() ) {
manipulator.setIndexChanged();
}
}
/**
* Switches the current Directory, what is going to be returned by the mock DirectoryProvider
*/
public void swithDirectory() {
int index = currentDirectoryIndex.incrementAndGet();
currentDirectory = directories[index % NUM_DIRECTORY_PROVIDERS];
}
public boolean isReaderCurrent(MockIndexReader reader) {
//avoid usage of allReaders or test would be useless
for ( PerDirectoryLatestReader latest : currentReaders.values() ) {
IndexReader latestReader = latest.current.reader;
if ( latestReader == reader ) {
return true;
}
}
return false;
}
@Override
protected DirectoryReader readerFactory(Directory directory) throws IOException {
TestManipulatorPerDP manipulatorPerDP = manipulators.get( directory );
if ( !manipulatorPerDP.isReaderCreated.compareAndSet( false, true ) ) {
throw new IllegalStateException( "IndexReader created twice" );
}
else {
return new MockIndexReader( manipulatorPerDP.isIndexReaderCurrent );
}
}
public void initialize() {
super.initialize( new MockDirectoryBasedIndexManager(), null );
}
public boolean areAllOldReferencesGone() {
int numReferencesReaders = allReaders.size();
int numExpectedActiveReaders = manipulators.size();
return numReferencesReaders == numExpectedActiveReaders;
}
public List<MockIndexReader> getCreatedIndexReaders() {
return createdReadersHistory;
}
/**
* Use our special DirectoryProvider to emulate index switching and dirtyness.
*/
public class MockDirectoryBasedIndexManager extends DirectoryBasedIndexManager {
private final MockDirectoryProvider provider = new MockDirectoryProvider();
@Override
public DirectoryProvider getDirectoryProvider() {
return provider;
}
}
public class MockDirectoryProvider implements DirectoryProvider<RAMDirectory> {
@Override
public void initialize(String directoryProviderName, Properties properties, BuildContext context) {
}
@Override
public void start(DirectoryBasedIndexManager indexManager) {
}
@Override
public void stop() {
}
@Override
public RAMDirectory getDirectory() {
return currentDirectory.getDirectory();
}
}
public class MockIndexReader extends DirectoryReader {
private final AtomicBoolean closed = new AtomicBoolean( false );
private final AtomicBoolean hasAlreadyBeenReOpened = new AtomicBoolean( false );
private final AtomicBoolean isIndexReaderCurrent;
MockIndexReader(AtomicBoolean isIndexReaderCurrent) throws IOException {
//make the super constructor happy as the class is "locked down"
super( new RAMDirectory(), new LeafReader[0] );
this.isIndexReaderCurrent = isIndexReaderCurrent;
if ( !isIndexReaderCurrent.compareAndSet( false, true ) ) {
throw new IllegalStateException( "Unnecessarily reopened" );
}
createdReadersHistory.add( this );
}
public final boolean isClosed() {
return closed.get();
}
@Override
protected void doClose() throws IOException {
boolean okToClose = closed.compareAndSet( false, true );
if ( !okToClose ) {
throw new IllegalStateException( "Attempt to close a closed IndexReader" );
}
if ( !hasAlreadyBeenReOpened.get() ) {
throw new IllegalStateException( "Attempt to close the most current IndexReader" );
}
}
@Override
public boolean hasDeletions() {
return false;//just something to make MultiReader constructor happy
}
@Override
protected DirectoryReader doOpenIfChanged() throws IOException {
if ( isIndexReaderCurrent.get() ) {
return null;
}
else {
if ( hasAlreadyBeenReOpened.compareAndSet( false, true ) ) {
return new MockIndexReader( isIndexReaderCurrent );
}
else {
throw new IllegalStateException( "Attempt to reopen an old IndexReader more than once" );
}
}
}
@Override
protected DirectoryReader doOpenIfChanged(IndexCommit commit) throws IOException {
return doOpenIfChanged();
}
@Override
protected DirectoryReader doOpenIfChanged(IndexWriter writer, boolean applyAllDeletes) throws IOException {
return doOpenIfChanged();
}
@Override
public long getVersion() {
return 0;
}
@Override
public boolean isCurrent() throws IOException {
return false;
}
@Override
public IndexCommit getIndexCommit() throws IOException {
return null;
}
}
}