/*
* 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.backend.lucene;
import org.hibernate.search.backend.spi.Work;
import org.hibernate.search.backend.spi.WorkType;
import org.hibernate.search.spi.SearchIntegrator;
import org.hibernate.search.spi.SearchIntegratorBuilder;
import org.hibernate.search.testsupport.BytemanHelper;
import org.hibernate.search.testsupport.BytemanHelper.BytemanAccessor;
import org.hibernate.search.testsupport.junit.SkipOnElasticsearch;
import org.hibernate.search.testsupport.setup.CountingErrorHandler;
import org.hibernate.search.testsupport.setup.SearchConfigurationForTest;
import org.hibernate.search.testsupport.setup.TransactionContextForTest;
import org.jboss.byteman.contrib.bmunit.BMRule;
import org.jboss.byteman.contrib.bmunit.BMRules;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@RunWith(BMUnitRunner.class)
@BMRules(rules = {
@BMRule(
name = "trackIndexWriterCommit",
targetClass = "org.apache.lucene.index.IndexWriter",
targetMethod = "commit()",
helper = "org.hibernate.search.testsupport.BytemanHelper",
action = "pushEvent(\"commit\")"
),
@BMRule(
name = "trackIndexWriterClose",
targetClass = "org.apache.lucene.index.IndexWriter",
targetMethod = "close()",
helper = "org.hibernate.search.testsupport.BytemanHelper",
action = "pushEvent(\"close\")"
),
@BMRule(
name = "trackUpdatesBeingApplied",
targetClass = "org.hibernate.search.backend.impl.lucene.LuceneBackendQueueTask",
targetMethod = "applyUpdates()",
helper = "org.hibernate.search.testsupport.BytemanHelper",
action = "pushEvent(\"applyUpdates\")"
),
} )
/**
* Verifies each Lucene backend is flushing and closing the IndexWriter as expected
* by its configuration, especially flags such as 'exclusive_index_use' and synchronous
* vs asynchronous configurations.
*
* @author Sanne Grinovero (C) 2015 Red Hat Inc.
*/
@Category(SkipOnElasticsearch.class) // IndexWriters are Lucene-specific
public class ResourcesClosedInOrderTest {
private static final int NUMBER_ENTITIES = 2;
@Rule
public BytemanAccessor byteman = BytemanHelper.createAccessor();
@Test
public void asyncExclusiveIndexResourcesOrderedShutdown() {
//The first "close" event is caused by index initialisation at boot.
//Then, we expect apply operations to be executed without any close nor commit.
expectOnConfiguration( true, true, "close", "applyUpdates", "applyUpdates", "close" );
}
@Test
public void asyncSharedIndexResourcesOrderedShutdown() {
//The first "close" event is caused by index initialisation at boot.
//But as the index is 'shared' the IndexWriter shall be closed after each write.
expectOnConfiguration( true, false, "close", "applyUpdates", "close", "applyUpdates", "close" );
}
@Test
public void synchExclusiveIndexResourcesOrderedShutdown() {
//The first "close" event is caused by index initialisation at boot.
//But as the indexing is synchronous (yet not using NRT), a commit is required after each write.
expectOnConfiguration( false, true, "close", "applyUpdates", "commit", "applyUpdates", "commit", "close" );
}
@Test
public void synchSharedIndexResourcesOrderedShutdown() {
//The first "close" event is caused by index initialisation at boot.
//But as the indexing is synchronous and shared, a 'close' is required after each write.
expectOnConfiguration( false, false, "close", "applyUpdates", "close", "applyUpdates", "close" );
}
private void expectOnConfiguration(boolean async, boolean exclusiveIndexing, String... expectedStack) {
SearchConfigurationForTest cfg = new SearchConfigurationForTest();
cfg.addProperty( "hibernate.search.default.worker.execution", async ? "async" : "sync" );
cfg.addProperty( "hibernate.search.default.exclusive_index_use", exclusiveIndexing ? "true" : "false" );
cfg.addProperty( "hibernate.search.error_handler", CountingErrorHandler.class.getName() );
cfg.addClass( Quote.class );
try ( SearchIntegrator searchIntegrator = new SearchIntegratorBuilder().configuration( cfg ).buildSearchIntegrator() ) {
final CountingErrorHandler errorHandler = (CountingErrorHandler) searchIntegrator.getErrorHandler();
writeData( searchIntegrator, NUMBER_ENTITIES );
//Check no errors happened in the asynchronous threads
assertEquals( 0, errorHandler.getTotalCount() );
}
//Now the SearchIntegrator was closed, let's unwind the recorder events and compare them with expectations:
for ( int i = 0; i < expectedStack.length; i++ ) {
//Check the events have been fired in the expected order
assertEquals( expectedStack[i], byteman.consumeNextRecordedEvent() );
}
//And no more events than those expected exist
assertTrue( byteman.isEventStackEmpty() );
}
private void writeData(SearchIntegrator searchIntegrator, int numberEntities) {
int entityIdGenerator = 0;
for ( int i = 0; i < numberEntities; i++ ) {
Integer id = Integer.valueOf( entityIdGenerator++ );
Quote quote = new Quote( id, "description" );
Work work = new Work( quote, id, WorkType.ADD, false );
TransactionContextForTest tc = new TransactionContextForTest();
searchIntegrator.getWorker().performWork( work, tc );
tc.end();
}
}
}