/* * 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; } } }