/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat, Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ 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.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.index.FieldInfos; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.index.TermEnum; import org.apache.lucene.index.TermFreqVector; import org.apache.lucene.index.TermPositions; import org.apache.lucene.index.TermVectorMapper; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.hibernate.search.indexes.impl.DirectoryBasedIndexManager; import org.hibernate.search.indexes.impl.SharingBufferReaderProvider; import org.hibernate.search.spi.BuildContext; import org.hibernate.search.store.DirectoryProvider; import org.hibernate.search.store.impl.RAMDirectoryProvider; /** * Testable extension of SharingBufferReaderProvider to make sure IndexReaders * are only opened when needed, and always correctly closed. * * @see SharingBufferIndexProviderTest * @author Sanne Grinovero */ 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 ), null, 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 IndexReader readerFactory(Directory directory) { TestManipulatorPerDP manipulatorPerDP = manipulators.get( directory ); if ( !manipulatorPerDP.isReaderCreated.compareAndSet( false, true ) ) { throw new IllegalStateException( "IndexReader created twice" ); } else { return new MockIndexReader( manipulatorPerDP.isIndexReaderCurrent ); } } @Override public void initialize(DirectoryBasedIndexManager indexManager, Properties props) { 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 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 IndexReader { private final AtomicBoolean closed = new AtomicBoolean( false ); private final AtomicBoolean hasAlreadyBeenReOpened = new AtomicBoolean( false ); private final AtomicBoolean isIndexReaderCurrent; MockIndexReader(AtomicBoolean isIndexReaderCurrent) { 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 synchronized IndexReader reopen() { if ( isIndexReaderCurrent.get() ) { return this; } 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 void doDelete(int docNum) { throw new UnsupportedOperationException(); } @Override protected void doSetNorm(int doc, String field, byte value) { throw new UnsupportedOperationException(); } @Override protected void doUndeleteAll() { throw new UnsupportedOperationException(); } @Override public int docFreq(Term t) { throw new UnsupportedOperationException(); } @Override public Document document(int n, FieldSelector fieldSelector) { throw new UnsupportedOperationException(); } @Override public TermFreqVector getTermFreqVector(int docNumber, String field) { throw new UnsupportedOperationException(); } @Override public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) { throw new UnsupportedOperationException(); } @Override public void getTermFreqVector(int docNumber, TermVectorMapper mapper) { throw new UnsupportedOperationException(); } @Override public TermFreqVector[] getTermFreqVectors(int docNumber) { throw new UnsupportedOperationException(); } @Override public boolean hasDeletions() { return false;//just something to make MultiReader constructor happy } @Override public boolean isDeleted(int n) { throw new UnsupportedOperationException(); } @Override public int maxDoc() { return 10;//just something to make MultiReader constructor happy } @Override public byte[] norms(String field) throws IOException { throw new UnsupportedOperationException(); } @Override public void norms(String field, byte[] bytes, int offset) { throw new UnsupportedOperationException(); } @Override public int numDocs() { throw new UnsupportedOperationException(); } @Override public TermDocs termDocs() { throw new UnsupportedOperationException(); } @Override public TermPositions termPositions() { throw new UnsupportedOperationException(); } @Override public TermEnum terms() throws IOException { throw new UnsupportedOperationException(); } @Override public TermEnum terms(Term t) throws IOException { throw new UnsupportedOperationException(); } @Override protected void doCommit(Map<String, String> commitUserData) { // no-op } @Override public FieldInfos getFieldInfos() { throw new UnsupportedOperationException(); } } }