diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/IndexManagerBackendContext.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/IndexManagerBackendContext.java index 5f56f491759..5cceaa1d99e 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/IndexManagerBackendContext.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/IndexManagerBackendContext.java @@ -53,7 +53,6 @@ import org.hibernate.search.engine.common.timing.spi.TimingSource; import org.hibernate.search.engine.reporting.FailureHandler; import org.hibernate.search.engine.search.loading.spi.SearchLoadingContextBuilder; -import org.hibernate.search.util.common.impl.SuppressingCloser; import org.hibernate.search.util.common.reporting.EventContext; import org.apache.lucene.search.similarities.Similarity; @@ -196,38 +195,19 @@ LuceneIndexSchemaManager createSchemaManager(SchemaManagementIndexManagerContext return new LuceneIndexSchemaManager( workFactory, context ); } - Shard createShard(LuceneIndexModel model, EventContext shardEventContext, DirectoryHolder directoryHolder, - IOStrategy ioStrategy, ConfigurationPropertySource propertySource) { - LuceneParallelWorkOrchestratorImpl managementOrchestrator; - LuceneSerialWorkOrchestratorImpl indexingOrchestrator; - IndexAccessorImpl indexAccessor = null; + IndexAccessorImpl createIndexAccessor(LuceneIndexModel model, EventContext shardEventContext, + DirectoryHolder directoryHolder, IOStrategy ioStrategy, + ConfigurationPropertySource propertySource) { String indexName = model.hibernateSearchName(); IndexWriterConfigSource writerConfigSource = IndexWriterConfigSource.create( similarity, model.getIndexingAnalyzer(), propertySource, shardEventContext ); - - try { - indexAccessor = ioStrategy.createIndexAccessor( - indexName, shardEventContext, directoryHolder, writerConfigSource - ); - managementOrchestrator = createIndexManagementOrchestrator( shardEventContext, indexAccessor ); - indexingOrchestrator = createIndexingOrchestrator( shardEventContext, indexAccessor ); - - Shard shard = new Shard( - shardEventContext, indexAccessor, - managementOrchestrator, indexingOrchestrator - ); - return shard; - } - catch (RuntimeException e) { - new SuppressingCloser( e ) - // No need to stop the orchestrators, we didn't start them - .push( indexAccessor ); - throw e; - } + return ioStrategy.createIndexAccessor( + indexName, shardEventContext, directoryHolder, writerConfigSource + ); } - private LuceneParallelWorkOrchestratorImpl createIndexManagementOrchestrator(EventContext eventContext, + LuceneParallelWorkOrchestratorImpl createIndexManagementOrchestrator(EventContext eventContext, IndexAccessorImpl indexAccessor) { return new LuceneParallelWorkOrchestratorImpl( "Lucene index management orchestrator for " + eventContext.render(), @@ -237,7 +217,7 @@ private LuceneParallelWorkOrchestratorImpl createIndexManagementOrchestrator(Eve ); } - private LuceneSerialWorkOrchestratorImpl createIndexingOrchestrator(EventContext eventContext, + LuceneSerialWorkOrchestratorImpl createIndexingOrchestrator(EventContext eventContext, IndexAccessorImpl indexAccessor) { return new LuceneSerialWorkOrchestratorImpl( "Lucene indexing orchestrator for " + eventContext.render(), diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/LuceneIndexManagerImpl.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/LuceneIndexManagerImpl.java index eff7ef1ecff..57583adc525 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/LuceneIndexManagerImpl.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/LuceneIndexManagerImpl.java @@ -29,6 +29,7 @@ import org.hibernate.search.engine.backend.scope.spi.IndexScopeBuilder; import org.hibernate.search.engine.backend.session.spi.BackendSessionContext; import org.hibernate.search.engine.backend.session.spi.DetachedBackendSessionContext; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.backend.work.execution.DocumentCommitStrategy; import org.hibernate.search.engine.backend.work.execution.DocumentRefreshStrategy; import org.hibernate.search.engine.backend.work.execution.spi.IndexIndexer; @@ -47,6 +48,8 @@ public class LuceneIndexManagerImpl implements IndexManagerImplementor, LuceneIndexManager, LuceneScopeIndexManagerContext { + private static final SavedState.Key SHARD_HOLDER_KEY = SavedState.key( "shard_holder" ); + private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final IndexManagerBackendContext backendContext; @@ -80,6 +83,18 @@ public String toString() { .toString(); } + @Override + public SavedState saveForRestart() { + return SavedState.builder() + .put( SHARD_HOLDER_KEY, shardHolder.saveForRestart() ) + .build(); + } + + @Override + public void preStart(IndexManagerStartContext context, SavedState savedState) { + shardHolder.preStart( context, savedState.get( SHARD_HOLDER_KEY ).orElse( SavedState.empty() ) ); + } + @Override public void start(IndexManagerStartContext context) { shardHolder.start( context ); @@ -135,7 +150,7 @@ public IndexScopeBuilder createScopeBuilder(BackendMappingContext mappingContext @Override public void addTo(IndexScopeBuilder builder) { - if ( ! ( builder instanceof LuceneIndexScopeBuilder ) ) { + if ( !( builder instanceof LuceneIndexScopeBuilder ) ) { throw log.cannotMixLuceneScopeWithOtherType( builder, this, backendContext.getEventContext() ); diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/Shard.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/Shard.java index ad146c218d5..53fb384e78b 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/Shard.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/Shard.java @@ -8,17 +8,30 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings; +import org.hibernate.search.backend.lucene.document.model.impl.LuceneIndexModel; import org.hibernate.search.backend.lucene.logging.impl.Log; +import org.hibernate.search.backend.lucene.lowlevel.directory.impl.DirectoryCreationContextImpl; +import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryCreationContext; +import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryHolder; +import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryProvider; +import org.hibernate.search.backend.lucene.lowlevel.index.impl.IOStrategy; import org.hibernate.search.backend.lucene.lowlevel.index.impl.IndexAccessorImpl; import org.hibernate.search.backend.lucene.orchestration.impl.LuceneParallelWorkOrchestrator; import org.hibernate.search.backend.lucene.orchestration.impl.LuceneParallelWorkOrchestratorImpl; import org.hibernate.search.backend.lucene.orchestration.impl.LuceneSerialWorkOrchestrator; import org.hibernate.search.backend.lucene.orchestration.impl.LuceneSerialWorkOrchestratorImpl; +import org.hibernate.search.engine.cfg.spi.ConfigurationProperty; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; +import org.hibernate.search.engine.environment.bean.BeanHolder; +import org.hibernate.search.engine.environment.bean.BeanReference; +import org.hibernate.search.engine.environment.bean.BeanResolver; +import org.hibernate.search.engine.reporting.spi.EventContexts; import org.hibernate.search.util.common.impl.Closer; -import org.hibernate.search.util.common.impl.SuppressingCloser; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import org.hibernate.search.util.common.reporting.EventContext; @@ -26,38 +39,85 @@ public final class Shard { + private static final ConfigurationProperty> DIRECTORY_TYPE = + ConfigurationProperty.forKey( LuceneIndexSettings.DIRECTORY_TYPE ) + .asBeanReference( DirectoryProvider.class ) + .withDefault( BeanReference.of( DirectoryProvider.class, LuceneIndexSettings.Defaults.DIRECTORY_TYPE ) ) + .build(); + + private static final SavedState.Key DIRECTORY_HOLDER_KEY = SavedState.key( "directory_holder" ); + private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final EventContext eventContext; - private final IndexAccessorImpl indexAccessor; - private final LuceneParallelWorkOrchestratorImpl managementOrchestrator; - private final LuceneSerialWorkOrchestratorImpl indexingOrchestrator; - - Shard(EventContext eventContext, IndexAccessorImpl indexAccessor, - LuceneParallelWorkOrchestratorImpl managementOrchestrator, - LuceneSerialWorkOrchestratorImpl indexingOrchestrator) { - this.eventContext = eventContext; - this.indexAccessor = indexAccessor; - this.managementOrchestrator = managementOrchestrator; - this.indexingOrchestrator = indexingOrchestrator; + private final Optional shardId; + private final IndexManagerBackendContext backendContext; + private final LuceneIndexModel model; + + private DirectoryHolder directoryHolder; + private IndexAccessorImpl indexAccessor; + private LuceneParallelWorkOrchestratorImpl managementOrchestrator; + private LuceneSerialWorkOrchestratorImpl indexingOrchestrator; + + private boolean savedForRestart = false; + + Shard(Optional shardId, IndexManagerBackendContext backendContext, LuceneIndexModel model) { + this.shardId = shardId; + this.backendContext = backendContext; + this.model = model; + } + + public SavedState saveForRestart() { + try { + return SavedState.builder() + .put( DIRECTORY_HOLDER_KEY, directoryHolder, DirectoryHolder::close ) + .build(); + } + finally { + savedForRestart = true; + } + } + + void preStart(ConfigurationPropertySource propertySource, BeanResolver beanResolver, SavedState savedState) { + Optional savedDirectoryHolder = savedState.get( Shard.DIRECTORY_HOLDER_KEY ); + try { + if ( savedDirectoryHolder.isPresent() ) { + directoryHolder = savedDirectoryHolder.get(); + } + else { + try ( BeanHolder directoryProviderHolder = + DIRECTORY_TYPE.getAndTransform( propertySource, beanResolver::resolve ) ) { + String indexName = model.hibernateSearchName(); + EventContext indexAndShardEventContext = EventContexts.fromIndexNameAndShardId( indexName, shardId ); + DirectoryCreationContext context = new DirectoryCreationContextImpl( indexAndShardEventContext, + indexName, shardId, beanResolver, + propertySource.withMask( "directory" ) ); + directoryHolder = directoryProviderHolder.get().createDirectoryHolder( context ); + } + directoryHolder.start(); + } + } + catch (IOException | RuntimeException e) { + throw log.unableToStartShard( e.getMessage(), e ); + } } void start(ConfigurationPropertySource propertySource) { + String indexName = model.hibernateSearchName(); + EventContext indexAndShardEventContext = EventContexts.fromIndexNameAndShardId( indexName, shardId ); try { - indexAccessor.start(); + IOStrategy ioStrategy = backendContext.createIOStrategy( propertySource ); + indexAccessor = backendContext.createIndexAccessor( model, indexAndShardEventContext, directoryHolder, + ioStrategy, propertySource ); + managementOrchestrator = + backendContext.createIndexManagementOrchestrator( indexAndShardEventContext, indexAccessor ); + indexingOrchestrator = + backendContext.createIndexingOrchestrator( indexAndShardEventContext, indexAccessor ); + managementOrchestrator.start( propertySource ); indexingOrchestrator.start( propertySource ); } - catch (IOException | RuntimeException e) { - new SuppressingCloser( e ) - .push( indexAccessor ) - .push( LuceneSerialWorkOrchestratorImpl::stop, indexingOrchestrator ) - .push( LuceneParallelWorkOrchestratorImpl::stop, managementOrchestrator ); - throw log.unableToInitializeIndexDirectory( - e.getMessage(), - eventContext, - e - ); + catch (RuntimeException e) { + throw log.unableToStartShard( e.getMessage(), e ); } } @@ -66,11 +126,21 @@ CompletableFuture preStop() { } void stop() { - try ( Closer closer = new Closer<>() ) { + try ( Closer closer = new Closer<>() ) { closer.push( LuceneSerialWorkOrchestratorImpl::stop, indexingOrchestrator ); closer.push( LuceneParallelWorkOrchestratorImpl::stop, managementOrchestrator ); // Close the index writer after the orchestrators, when we're sure all works have been performed closer.push( IndexAccessorImpl::close, indexAccessor ); + if ( !savedForRestart ) { + closer.push( DirectoryHolder::close, directoryHolder ); + } + } + catch (RuntimeException | IOException e) { + throw log.unableToShutdownShard( + e.getMessage(), + shardId.map( EventContexts::fromShardId ).orElse( null ), + e + ); } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardHolder.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardHolder.java index cb7860b018c..7238cd9ac82 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardHolder.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardHolder.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -16,6 +18,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; +import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings; import org.hibernate.search.backend.lucene.document.model.impl.LuceneIndexModel; import org.hibernate.search.backend.lucene.index.spi.ShardingStrategy; import org.hibernate.search.backend.lucene.lowlevel.reader.impl.DirectoryReaderCollector; @@ -25,14 +28,18 @@ import org.hibernate.search.backend.lucene.schema.management.impl.SchemaManagementIndexManagerContext; import org.hibernate.search.backend.lucene.work.execution.impl.WorkExecutionIndexManagerContext; import org.hibernate.search.engine.backend.index.spi.IndexManagerStartContext; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; import org.hibernate.search.engine.environment.bean.BeanHolder; +import org.hibernate.search.engine.reporting.spi.EventContexts; import org.hibernate.search.util.common.impl.Closer; import org.hibernate.search.util.common.impl.SuppressingCloser; class ShardHolder implements ReadIndexManagerContext, WorkExecutionIndexManagerContext, SchemaManagementIndexManagerContext { + private static final SavedState.Key> SHARDS_KEY = SavedState.key( "shards" ); + private final IndexManagerBackendContext backendContext; private final LuceneIndexModel model; @@ -50,28 +57,74 @@ public String toString() { return getClass().getSimpleName() + "[indexName=" + model.hibernateSearchName() + "]"; } - void start(IndexManagerStartContext startContext) { - ConfigurationPropertySource propertySource = startContext.configurationPropertySource(); + public SavedState saveForRestart() { + HashMap states = new HashMap<>(); + for ( Map.Entry shard : shards.entrySet() ) { + states.put( shard.getKey(), shard.getValue().saveForRestart() ); + } + return SavedState.builder().put( SHARDS_KEY, states ).build(); + } + private ConfigurationPropertySource toShardPropertySource(ConfigurationPropertySource indexPropertySource, String shardIdOrNull) { + return shardIdOrNull != null + ? indexPropertySource.withMask( LuceneIndexSettings.SHARDS ).withMask( shardIdOrNull ) + .withFallback( indexPropertySource ) + : indexPropertySource; + } + + void preStart(IndexManagerStartContext startContext, SavedState savedState) { + ConfigurationPropertySource indexPropertySource = startContext.configurationPropertySource(); try { ShardingStrategyInitializationContextImpl initializationContext = - new ShardingStrategyInitializationContextImpl( backendContext, model, startContext, propertySource ); + new ShardingStrategyInitializationContextImpl( backendContext, model, startContext, indexPropertySource ); + Map states = savedState.get( SHARDS_KEY ).orElse( Collections.emptyMap() ); + this.shardingStrategyHolder = initializationContext.create( shards ); - if ( startContext.failureCollector().hasFailure() ) { - // At least one shard creation failed; abort and don't even try to start shards. - return; + for ( Map.Entry entry : shards.entrySet() ) { + String shardId = entry.getKey(); + Shard shard = entry.getValue(); + ConfigurationPropertySource shardPropertySource = toShardPropertySource( indexPropertySource, shardId ); + try { + shard.preStart( shardPropertySource, startContext.beanResolver(), + states.getOrDefault( entry.getKey(), SavedState.empty() ) ); + } + catch (RuntimeException e) { + startContext.failureCollector() + .withContext( shardId == null ? null : EventContexts.fromShardId( shardId ) ) + .add( e ); + } } + } + catch (RuntimeException e) { + new SuppressingCloser( e ) + .pushAll( Shard::stop, shards.values() ); + shards.clear(); + throw e; + } + } - for ( Shard shard : shards.values() ) { - shard.start( propertySource ); - managementOrchestrators.add( shard.managementOrchestrator() ); + void start(IndexManagerStartContext startContext) { + ConfigurationPropertySource indexPropertySource = startContext.configurationPropertySource(); + try { + for ( Map.Entry entry : shards.entrySet() ) { + String shardId = entry.getKey(); + Shard shard = entry.getValue(); + ConfigurationPropertySource shardPropertySource = toShardPropertySource( indexPropertySource, shardId ); + try { + shard.start( shardPropertySource ); + managementOrchestrators.add( shard.managementOrchestrator() ); + } + catch (RuntimeException e) { + startContext.failureCollector() + .withContext( shardId == null ? null : EventContexts.fromShardId( shardId ) ) + .add( e ); + } } } catch (RuntimeException e) { new SuppressingCloser( e ) .pushAll( Shard::stop, shards.values() ); - shards.clear(); managementOrchestrators.clear(); throw e; } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardingStrategyInitializationContextImpl.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardingStrategyInitializationContextImpl.java index 4ac2bd0f008..477325c3f9e 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardingStrategyInitializationContextImpl.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/index/impl/ShardingStrategyInitializationContextImpl.java @@ -17,22 +17,13 @@ import org.hibernate.search.backend.lucene.index.spi.ShardingStrategy; import org.hibernate.search.backend.lucene.index.spi.ShardingStrategyInitializationContext; import org.hibernate.search.backend.lucene.logging.impl.Log; -import org.hibernate.search.backend.lucene.lowlevel.directory.impl.DirectoryCreationContextImpl; -import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryCreationContext; -import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryHolder; -import org.hibernate.search.backend.lucene.lowlevel.directory.spi.DirectoryProvider; -import org.hibernate.search.backend.lucene.lowlevel.index.impl.IOStrategy; import org.hibernate.search.engine.backend.index.spi.IndexManagerStartContext; import org.hibernate.search.engine.cfg.spi.ConfigurationProperty; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; import org.hibernate.search.engine.environment.bean.BeanHolder; import org.hibernate.search.engine.environment.bean.BeanReference; import org.hibernate.search.engine.environment.bean.BeanResolver; -import org.hibernate.search.engine.reporting.spi.ContextualFailureCollector; -import org.hibernate.search.engine.reporting.spi.EventContexts; -import org.hibernate.search.util.common.impl.SuppressingCloser; import org.hibernate.search.util.common.logging.impl.LoggerFactory; -import org.hibernate.search.util.common.reporting.EventContext; class ShardingStrategyInitializationContextImpl implements ShardingStrategyInitializationContext { @@ -46,16 +37,9 @@ class ShardingStrategyInitializationContextImpl implements ShardingStrategyIniti ) ) .build(); - private static final ConfigurationProperty> DIRECTORY_TYPE = - ConfigurationProperty.forKey( LuceneIndexSettings.DIRECTORY_TYPE ) - .asBeanReference( DirectoryProvider.class ) - .withDefault( BeanReference.of( DirectoryProvider.class, LuceneIndexSettings.Defaults.DIRECTORY_TYPE ) ) - .build(); - private final IndexManagerBackendContext backendContext; private final LuceneIndexModel model; private final IndexManagerStartContext startContext; - private final ConfigurationPropertySource indexPropertySource; private final ConfigurationPropertySource shardingPropertySource; private Set shardIdentifiers = new LinkedHashSet<>(); @@ -66,7 +50,6 @@ class ShardingStrategyInitializationContextImpl implements ShardingStrategyIniti this.backendContext = backendContext; this.model = model; this.startContext = startContext; - this.indexPropertySource = indexPropertySource; this.shardingPropertySource = indexPropertySource.withMask( "sharding" ); } @@ -104,7 +87,7 @@ public BeanHolder create(Map shardCol if ( shardIdentifiers == null ) { // Sharding is disabled => single shard - contributeShardWithSilentFailure( shardCollector, Optional.empty() ); + contributeShard( shardCollector, Optional.empty() ); return null; } @@ -115,41 +98,14 @@ public BeanHolder create(Map shardCol } for ( String shardIdentifier : shardIdentifiers ) { - contributeShardWithSilentFailure( shardCollector, Optional.of( shardIdentifier ) ); + contributeShard( shardCollector, Optional.of( shardIdentifier ) ); } return shardingStrategyHolder; } - private void contributeShardWithSilentFailure(Map shardCollector, Optional shardId) { - EventContext shardEventContext = EventContexts.fromIndexNameAndShardId( indexName(), shardId ); - ConfigurationPropertySource shardPropertySource = - shardId.isPresent() ? - indexPropertySource.withMask( LuceneIndexSettings.SHARDS ).withMask( shardId.get() ) - .withFallback( indexPropertySource ) - : indexPropertySource; - - DirectoryHolder directoryHolder = null; - try ( BeanHolder directoryProviderHolder = - DIRECTORY_TYPE.getAndTransform( shardPropertySource, startContext.beanResolver()::resolve ) ) { - DirectoryCreationContext context = new DirectoryCreationContextImpl( shardEventContext, - indexName(), shardId, beanResolver(), shardPropertySource.withMask( "directory" ) ); - directoryHolder = directoryProviderHolder.get().createDirectoryHolder( context ); - - IOStrategy ioStrategy = backendContext.createIOStrategy( shardPropertySource ); - - Shard shard = backendContext.createShard( model, shardEventContext, directoryHolder, ioStrategy, - shardPropertySource ); - shardCollector.put( shardId.orElse( null ), shard ); - } - catch (RuntimeException e) { - new SuppressingCloser( e ).push( directoryHolder ); - - ContextualFailureCollector failureCollector = startContext.failureCollector(); - if ( shardId.isPresent() ) { - failureCollector = failureCollector.withContext( EventContexts.fromShardId( shardId.get() ) ); - } - failureCollector.add( e ); - } + private void contributeShard(Map shardCollector, Optional shardId) { + Shard shard = new Shard( shardId, backendContext, model ); + shardCollector.put( shardId.orElse( null ), shard ); } } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java index 1d99b1e9c67..b293db8056d 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/logging/impl/Log.java @@ -299,8 +299,8 @@ SearchException indexManagerUnwrappingWithUnknownType(@FormatWith(ClassFormatter value = "Invalid search projection: '%1$s'. You must build the projection from a Lucene search scope.") SearchException cannotMixLuceneSearchQueryWithOtherProjections(SearchProjection projection); - @Message(id = ID_OFFSET + 61, value = "Unable to shut down index accessor: %1$s") - SearchException unableToShutdownIndexAccessor(String causeMessage, @Cause Exception cause); + @Message(id = ID_OFFSET + 61, value = "Unable to shut down index: %1$s") + SearchException unableToShutdownShard(String causeMessage, @Param EventContext context, @Cause Exception cause); @Message(id = ID_OFFSET + 62, value = "No built-in index field type for class: '%1$s'.") SearchException cannotGuessFieldType(@FormatWith(ClassFormatter.class) Class inputType, @Param EventContext context); @@ -592,4 +592,8 @@ SearchException indexSchemaNamedPredicateNameConflict(String relativeFilterName, value = "Offset + limit should be lower than Integer.MAX_VALUE, offset: '%1$s', limit: '%2$s'.") IOException offsetLimitExceedsMaxValue(int offset, Integer limit); + @Message(id = ID_OFFSET + 154, + value = "Unable to start index: %1$s") + SearchException unableToStartShard(String causeMessage, @Cause Exception cause); + } diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/directory/impl/LocalHeapDirectoryHolder.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/directory/impl/LocalHeapDirectoryHolder.java index 1e5df79766e..fe7c901395d 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/directory/impl/LocalHeapDirectoryHolder.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/directory/impl/LocalHeapDirectoryHolder.java @@ -26,7 +26,7 @@ final class LocalHeapDirectoryHolder implements DirectoryHolder { @Override public void start() { - this.directory = new ByteBuffersDirectory( lockFactory ); + directory = new ByteBuffersDirectory( lockFactory ); } @Override diff --git a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorImpl.java b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorImpl.java index 5cbaa4c34db..34797fbb053 100644 --- a/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorImpl.java +++ b/backend/lucene/src/main/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorImpl.java @@ -49,19 +49,11 @@ public IndexAccessorImpl(EventContext eventContext, this.indexReaderProvider = indexReaderProvider; } - public void start() throws IOException { - directoryHolder.start(); - } - @Override - public void close() { + public void close() throws IOException { try ( Closer closer = new Closer<>() ) { closer.push( IndexWriterProvider::clear, indexWriterProvider ); closer.push( IndexReaderProvider::clear, indexReaderProvider ); - closer.push( DirectoryHolder::close, directoryHolder ); - } - catch (RuntimeException | IOException e) { - throw log.unableToShutdownIndexAccessor( e.getMessage(), e ); } } diff --git a/backend/lucene/src/test/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorTest.java b/backend/lucene/src/test/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorTest.java index 9522acbb08e..7fa2faf1f2f 100644 --- a/backend/lucene/src/test/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorTest.java +++ b/backend/lucene/src/test/java/org/hibernate/search/backend/lucene/lowlevel/index/impl/IndexAccessorTest.java @@ -64,8 +64,6 @@ public class IndexAccessorTest { public void start() throws IOException { accessor = new IndexAccessorImpl( indexEventContext, directoryHolderMock, indexWriterProviderMock, indexReaderProviderMock ); - accessor.start(); - verify( directoryHolderMock ).start(); } @After diff --git a/engine/src/main/java/org/hibernate/search/engine/backend/index/spi/IndexManagerImplementor.java b/engine/src/main/java/org/hibernate/search/engine/backend/index/spi/IndexManagerImplementor.java index 6a829d95d23..c3c75984702 100644 --- a/engine/src/main/java/org/hibernate/search/engine/backend/index/spi/IndexManagerImplementor.java +++ b/engine/src/main/java/org/hibernate/search/engine/backend/index/spi/IndexManagerImplementor.java @@ -9,6 +9,7 @@ import java.util.concurrent.CompletableFuture; import org.hibernate.search.engine.backend.schema.management.spi.IndexSchemaManager; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.backend.work.execution.DocumentCommitStrategy; import org.hibernate.search.engine.backend.work.execution.DocumentRefreshStrategy; import org.hibernate.search.engine.backend.index.IndexManager; @@ -28,14 +29,34 @@ */ public interface IndexManagerImplementor { + default SavedState saveForRestart() { + return SavedState.empty(); + } + /** - * Start any resource necessary to operate the index manager at runtime. + * Starts a subset of resources that are necessary to operate the index manager at runtime, and are expected to be reused upon restarts. + * The resources may be retrieved them from the saved state, + * or created if they are not present in the saved state. *

* Called by the engine once after bootstrap, after * {@link org.hibernate.search.engine.backend.spi.BackendImplementor#start(BackendStartContext)} * was called on the corresponding backend. * * @param context The start context. + * @param savedState The saved state returned by the corresponding index manager in the Hibernate Search integration + * being restarted, or {@link SavedState#empty()} on the first start. + */ + default void preStart(IndexManagerStartContext context, SavedState savedState) { + // do nothing by default + } + + /** + * Start any resource necessary to operate the index manager at runtime. + *

+ * Called by the engine once just after + * {@link #preStart(IndexManagerStartContext, SavedState)}. + * + * @param context The start context. */ void start(IndexManagerStartContext context); diff --git a/engine/src/main/java/org/hibernate/search/engine/common/impl/IndexManagerNonStartedState.java b/engine/src/main/java/org/hibernate/search/engine/common/impl/IndexManagerNonStartedState.java index 59faa480bb2..1f393832ac1 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/impl/IndexManagerNonStartedState.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/impl/IndexManagerNonStartedState.java @@ -7,6 +7,7 @@ package org.hibernate.search.engine.common.impl; import org.hibernate.search.engine.backend.index.spi.IndexManagerImplementor; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.cfg.impl.ConfigurationPropertySourceExtractor; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; import org.hibernate.search.engine.environment.bean.BeanResolver; @@ -20,6 +21,10 @@ class IndexManagerNonStartedState { private final ConfigurationPropertySourceExtractor propertySourceExtractor; private final IndexManagerImplementor indexManager; + // created on pre-start + private IndexManagerStartContextImpl startContext; + private ContextualFailureCollector indexFailureCollector; + IndexManagerNonStartedState(EventContext eventContext, ConfigurationPropertySourceExtractor propertySourceExtractor, IndexManagerImplementor indexManager) { @@ -32,14 +37,22 @@ void closeOnFailure() { indexManager.stop(); } - IndexManagerImplementor start(RootFailureCollector rootFailureCollector, - BeanResolver beanResolver, - ConfigurationPropertySource rootPropertySource) { - ContextualFailureCollector indexFailureCollector = rootFailureCollector.withContext( eventContext ); + void preStart(RootFailureCollector rootFailureCollector, BeanResolver beanResolver, + ConfigurationPropertySource rootPropertySource, SavedState savedState) { + indexFailureCollector = rootFailureCollector.withContext( eventContext ); ConfigurationPropertySource indexPropertySource = propertySourceExtractor.extract( rootPropertySource ); - IndexManagerStartContextImpl startContext = new IndexManagerStartContextImpl( + startContext = new IndexManagerStartContextImpl( indexFailureCollector, beanResolver, indexPropertySource ); + try { + indexManager.preStart( startContext, savedState ); + } + catch (RuntimeException e) { + indexFailureCollector.add( e ); + } + } + + IndexManagerImplementor start() { try { indexManager.start( startContext ); } @@ -48,5 +61,4 @@ IndexManagerImplementor start(RootFailureCollector rootFailureCollector, } return indexManager; // The index is now started } - } diff --git a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilder.java b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilder.java index b8f5e94ac6e..5c46709f906 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilder.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilder.java @@ -11,6 +11,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.hibernate.search.engine.cfg.EngineSettings; import org.hibernate.search.engine.cfg.spi.ConfigurationProperty; @@ -63,12 +64,15 @@ public class SearchIntegrationBuilder implements SearchIntegration.Builder { .build(); private final SearchIntegrationEnvironment environment; + private final Optional previousIntegration; private final Map, MappingInitiator> mappingInitiators = new LinkedHashMap<>(); private boolean frozen = false; - public SearchIntegrationBuilder(SearchIntegrationEnvironment environment) { + public SearchIntegrationBuilder(SearchIntegrationEnvironment environment, + Optional previousIntegration) { this.environment = environment; + this.previousIntegration = previousIntegration; environment.propertyChecker().beforeBoot(); } @@ -195,7 +199,7 @@ public SearchIntegrationPartialBuildState prepareBuild() { indexManagerBuildingStateHolder.getBackendNonStartedStates(), indexManagerBuildingStateHolder.getIndexManagersNonStartedStates(), environment.propertyChecker(), - engineThreads, timingSource + engineThreads, timingSource, previousIntegration ); } catch (RuntimeException e) { diff --git a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationImpl.java b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationImpl.java index 607fef6c91c..fdd427b5119 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationImpl.java @@ -7,7 +7,9 @@ package org.hibernate.search.engine.common.impl; import java.lang.invoke.MethodHandles; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -16,8 +18,10 @@ import org.hibernate.search.engine.backend.index.IndexManager; import org.hibernate.search.engine.backend.index.spi.IndexManagerImplementor; import org.hibernate.search.engine.backend.spi.BackendImplementor; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.common.resources.impl.EngineThreads; import org.hibernate.search.engine.common.spi.SearchIntegration; +import org.hibernate.search.engine.common.spi.SearchIntegrationEnvironment; import org.hibernate.search.engine.common.timing.spi.TimingSource; import org.hibernate.search.engine.environment.bean.BeanHolder; import org.hibernate.search.engine.environment.bean.spi.BeanProvider; @@ -38,6 +42,8 @@ public class SearchIntegrationImpl implements SearchIntegration { + static final SavedState.Key> INDEX_MANAGERS_KEY = SavedState.key( "index_managers" ); + private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final BeanProvider beanProvider; @@ -95,6 +101,19 @@ public IndexManager indexManager(String indexManagerName) { return indexManager.toAPI(); } + SavedState saveForRestart() { + HashMap states = new HashMap<>(); + for ( Map.Entry indexManager : indexManagers.entrySet() ) { + states.put( indexManager.getKey(), indexManager.getValue().saveForRestart() ); + } + return SavedState.builder().put( INDEX_MANAGERS_KEY, states ).build(); + } + + @Override + public Builder restartBuilder(SearchIntegrationEnvironment environment) { + return new SearchIntegrationBuilder( environment, Optional.of( this ) ); + } + @Override public void close() { RootFailureCollector rootFailureCollector = diff --git a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationPartialBuildStateImpl.java b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationPartialBuildStateImpl.java index d5b9d85a9ae..feb4da904be 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationPartialBuildStateImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationPartialBuildStateImpl.java @@ -6,12 +6,17 @@ */ package org.hibernate.search.engine.common.impl; +import static org.hibernate.search.engine.common.impl.SearchIntegrationImpl.INDEX_MANAGERS_KEY; + +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.hibernate.search.engine.backend.index.spi.IndexManagerImplementor; import org.hibernate.search.engine.backend.spi.BackendImplementor; +import org.hibernate.search.engine.common.resources.spi.SavedState; import org.hibernate.search.engine.cfg.spi.ConfigurationPropertyChecker; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; import org.hibernate.search.engine.common.resources.impl.EngineThreads; @@ -57,6 +62,7 @@ class SearchIntegrationPartialBuildStateImpl implements SearchIntegrationPartial private final EngineThreads engineThreads; private final TimingSource timingSource; + private final Optional previousIntegration; SearchIntegrationPartialBuildStateImpl( BeanProvider beanProvider, BeanResolver beanResolver, @@ -66,7 +72,8 @@ class SearchIntegrationPartialBuildStateImpl implements SearchIntegrationPartial Map nonStartedBackends, Map nonStartedIndexManagers, ConfigurationPropertyChecker partialConfigurationPropertyChecker, - EngineThreads engineThreads, TimingSource timingSource) { + EngineThreads engineThreads, TimingSource timingSource, + Optional previousIntegration) { this.beanProvider = beanProvider; this.beanResolver = beanResolver; this.failureHandlerHolder = failureHandlerHolder; @@ -77,6 +84,7 @@ class SearchIntegrationPartialBuildStateImpl implements SearchIntegrationPartial this.partialConfigurationPropertyChecker = partialConfigurationPropertyChecker; this.engineThreads = engineThreads; this.timingSource = timingSource; + this.previousIntegration = previousIntegration; } @Override @@ -94,6 +102,10 @@ public void closeOnFailure() { closer.pushAll( BeanProvider::close, beanProvider ); closer.pushAll( EngineThreads::onStop, engineThreads ); closer.pushAll( TimingSource::stop, timingSource ); + + if ( previousIntegration.isPresent() ) { + closer.pushAll( SearchIntegration::close, previousIntegration.get() ); + } } } @@ -164,11 +176,26 @@ public SearchIntegration finalizeIntegration() { } failureCollector.checkNoFailure(); + // Pre-Start indexes + try ( SavedState previousIntegrationSavedState = + previousIntegration.map( SearchIntegrationImpl::saveForRestart ).orElse( SavedState.empty() ) ) { + for ( Map.Entry entry : nonStartedIndexManagers.entrySet() ) { + SavedState savedState = previousIntegrationSavedState.get( INDEX_MANAGERS_KEY ) + .orElse( Collections.emptyMap() ).getOrDefault( entry.getKey(), SavedState.empty() ); + entry.getValue().preStart( failureCollector, beanResolver, propertySource, savedState ); + } + } + failureCollector.checkNoFailure(); + + if ( previousIntegration.isPresent() ) { + previousIntegration.get().close(); + } + // Start indexes for ( Map.Entry entry : nonStartedIndexManagers.entrySet() ) { startedIndexManagers.put( entry.getKey(), - entry.getValue().start( failureCollector, beanResolver, propertySource ) + entry.getValue().start() ); } failureCollector.checkNoFailure(); diff --git a/engine/src/main/java/org/hibernate/search/engine/common/resources/spi/SavedState.java b/engine/src/main/java/org/hibernate/search/engine/common/resources/spi/SavedState.java new file mode 100644 index 00000000000..d81e913c579 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/common/resources/spi/SavedState.java @@ -0,0 +1,152 @@ +/* + * 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 . + */ +package org.hibernate.search.engine.common.resources.spi; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.hibernate.search.util.common.impl.Closer; +import org.hibernate.search.util.common.impl.Contracts; +import org.hibernate.search.util.common.spi.ClosingOperator; + +public class SavedState implements AutoCloseable { + + private static final SavedState EMPTY = new SavedState.Builder().build(); + + public static SavedState empty() { + return EMPTY; + } + + private final Map, SavedValue> content; + + private SavedState(Builder builder) { + this.content = builder.content; + } + + @SuppressWarnings("unchecked") // values have always the corresponding key generic type + public Optional get(Key key) { + SavedValue savedValue = content.get( key ); + if ( savedValue == null ) { + return Optional.empty(); + } + + T value = (T) savedValue.value(); + return Optional.ofNullable( value ); + } + + public static Key key(String name) { + Contracts.assertNotNullNorEmpty( name, "name" ); + return new Key<>( name ); + } + + @Override + public void close() { + try ( Closer closer = new Closer<>() ) { + closer.pushAll( SavedValue::close, content.values() ); + } + } + + public static final class Key { + + private final String name; + + private Key(String name) { + this.name = name; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + name + "]"; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Key key = (Key) o; + return Objects.equals( name, key.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } + + public static Builder builder() { + return new Builder(); + } + + private static void closeAll(Map map) { + try ( Closer closer = new Closer<>() ) { + closer.pushAll( SavedState::close, map.values() ); + } + } + + public static final class Builder { + + private final Map, SavedValue> content = new LinkedHashMap<>(); + + private Builder() { + } + + public Builder put(Key key, SavedState value) { + return put( key, value, SavedState::close ); + } + + public Builder put(SavedState.Key> key, Map value) { + return put( key, value, SavedState::closeAll ); + } + + // values have always the corresponding key generic type + public Builder put(Key key, T value, ClosingOperator operator) { + content.put( key, new SavedValue<>( value, operator ) ); + return this; + } + + public SavedState build() { + return new SavedState( this ); + } + } + + public static final class SavedValue { + + private final T value; + private final ClosingOperator operator; + + private boolean close = true; + + public SavedValue(T value, ClosingOperator operator) { + this.value = value; + this.operator = operator; + } + + public T value() { + close = false; + return value; + } + + public void close() { + if ( !close ) { + return; + } + + try { + operator.close( value ); + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/common/spi/SearchIntegration.java b/engine/src/main/java/org/hibernate/search/engine/common/spi/SearchIntegration.java index 2acf89d82cc..ed75b6a7e0f 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/spi/SearchIntegration.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/spi/SearchIntegration.java @@ -6,6 +6,8 @@ */ package org.hibernate.search.engine.common.spi; +import java.util.Optional; + import org.hibernate.search.engine.backend.Backend; import org.hibernate.search.engine.backend.index.IndexManager; import org.hibernate.search.engine.common.impl.SearchIntegrationBuilder; @@ -21,11 +23,13 @@ public interface SearchIntegration extends AutoCloseable { IndexManager indexManager(String indexManagerName); + Builder restartBuilder(SearchIntegrationEnvironment environment); + @Override void close(); static Builder builder(SearchIntegrationEnvironment environment) { - return new SearchIntegrationBuilder( environment ); + return new SearchIntegrationBuilder( environment, Optional.empty() ); } interface Builder { diff --git a/engine/src/main/java/org/hibernate/search/engine/reporting/spi/RootFailureCollector.java b/engine/src/main/java/org/hibernate/search/engine/reporting/spi/RootFailureCollector.java index 973fdf610b2..4937ae57e81 100644 --- a/engine/src/main/java/org/hibernate/search/engine/reporting/spi/RootFailureCollector.java +++ b/engine/src/main/java/org/hibernate/search/engine/reporting/spi/RootFailureCollector.java @@ -98,6 +98,9 @@ protected NonRootFailureCollector(NonRootFailureCollector parent) { @Override public synchronized ContextualFailureCollectorImpl withContext(EventContext context) { + if ( context == null ) { + return withDefaultContext(); + } List elements = context.elements(); try { NonRootFailureCollector failureCollector = this; @@ -116,6 +119,9 @@ public synchronized ContextualFailureCollectorImpl withContext(EventContext cont @Override public synchronized ContextualFailureCollectorImpl withContext(EventContextElement contextElement) { + if ( contextElement == null ) { + return withDefaultContext(); + } return children.computeIfAbsent( contextElement, element -> new ContextualFailureCollectorImpl( this, element ) diff --git a/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/index/LuceneIndexRestartFromPreviousIntegrationIT.java b/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/index/LuceneIndexRestartFromPreviousIntegrationIT.java new file mode 100644 index 00000000000..1a287c722e2 --- /dev/null +++ b/integrationtest/backend/lucene/src/test/java/org/hibernate/search/integrationtest/backend/lucene/index/LuceneIndexRestartFromPreviousIntegrationIT.java @@ -0,0 +1,98 @@ +/* + * 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 . + */ +package org.hibernate.search.integrationtest.backend.lucene.index; + +import static org.hibernate.search.util.impl.integrationtest.common.assertion.SearchResultAssert.assertThatQuery; + +import java.util.Arrays; +import java.util.List; + +import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings; +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaElement; +import org.hibernate.search.engine.common.spi.SearchIntegration; +import org.hibernate.search.integrationtest.backend.tck.testsupport.util.rule.SearchSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.BulkIndexer; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.SimpleMappedIndex; +import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingSchemaManagementStrategy; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class LuceneIndexRestartFromPreviousIntegrationIT { + + @Parameterized.Parameters(name = "{0}") + public static List params() { + return Arrays.asList( "local-heap", "local-filesystem" ); + } + + @Parameterized.Parameter + public String directoryType; + + @Rule + public final SearchSetupHelper setupHelper = new SearchSetupHelper(); + + private final SimpleMappedIndex indexV1 = SimpleMappedIndex.of( IndexBindingV1::new ); + private final SimpleMappedIndex indexV2 = SimpleMappedIndex.of( IndexBindingV2::new ); + + @Test + public void addNewFieldOnExistingIndex() { + SearchIntegration integrationV1 = setupHelper.start() + .withSchemaManagement( StubMappingSchemaManagementStrategy.DROP_AND_CREATE_ON_STARTUP_ONLY ) + .withIndex( indexV1 ) + .withBackendProperty( LuceneIndexSettings.DIRECTORY_TYPE, directoryType ) + .setup(); + + BulkIndexer indexer1 = indexV1.bulkIndexer(); + indexer1.add( "1", doc -> { + doc.addValue( indexV1.binding().name, "Fabio" ); + } ); + indexer1.join(); + + assertThatQuery( indexV1.query().where( f -> f.match().field( "name" ).matching( "Fabio" ) ) ) + .hasDocRefHitsAnyOrder( indexV1.typeName(), "1" ); + + setupHelper.start() + .withSchemaManagement( StubMappingSchemaManagementStrategy.DROP_ON_SHUTDOWN_ONLY ) + .withIndex( indexV2 ) + .withBackendProperty( LuceneIndexSettings.DIRECTORY_TYPE, directoryType ) + .setup( integrationV1 ); + + BulkIndexer indexer2 = indexV2.bulkIndexer(); + indexer2.add( "2", doc -> { + doc.addValue( indexV2.binding().name, "Fabio" ); + doc.addValue( indexV2.binding().surname, "Ercoli" ); + } ); + indexer2.join(); + + assertThatQuery( indexV2.query().where( f -> f.match().field( "surname" ).matching( "Ercoli" ) ) ) + .hasDocRefHitsAnyOrder( indexV2.typeName(), "2" ); + assertThatQuery( indexV2.query().where( f -> f.match().field( "name" ).matching( "Fabio" ) ) ) + .hasDocRefHitsAnyOrder( indexV2.typeName(), "1", "2" ); + } + + private static class IndexBindingV1 { + final IndexFieldReference name; + + IndexBindingV1(IndexSchemaElement root) { + name = root.field( "name", c -> c.asString() ).toReference(); + } + } + + private static class IndexBindingV2 { + final IndexFieldReference name; + final IndexFieldReference surname; + + IndexBindingV2(IndexSchemaElement root) { + name = root.field( "name", c -> c.asString() ).toReference(); + surname = root.field( "surname", c -> c.asString() ).toReference(); + } + } +} diff --git a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/rule/SearchSetupHelper.java b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/rule/SearchSetupHelper.java index e124981b274..3137904c4ac 100644 --- a/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/rule/SearchSetupHelper.java +++ b/integrationtest/backend/tck/src/main/java/org/hibernate/search/integrationtest/backend/tck/testsupport/util/rule/SearchSetupHelper.java @@ -229,13 +229,19 @@ public SetupContext withSchemaManagement(StubMappingSchemaManagementStrategy sch } public PartialSetup setupFirstPhaseOnly() { + return setupFirstPhaseOnly( Optional.empty() ); + } + + public PartialSetup setupFirstPhaseOnly(Optional previousIntegration) { SearchIntegrationEnvironment environment = SearchIntegrationEnvironment.builder( propertySource, unusedPropertyChecker ) .beanProvider( beanProvider ) .build(); environments.add( environment ); - SearchIntegration.Builder integrationBuilder = SearchIntegration.builder( environment ); + SearchIntegration.Builder integrationBuilder = (previousIntegration.isPresent()) ? + previousIntegration.get().restartBuilder( environment ) : + SearchIntegration.builder( environment ); StubMappingInitiator initiator = new StubMappingInitiator( tenancyMode ); mappedIndexes.forEach( initiator::add ); @@ -265,6 +271,10 @@ public SearchIntegration setup() { return setupFirstPhaseOnly().doSecondPhase(); } + public SearchIntegration setup(SearchIntegration previousIntegration) { + return setupFirstPhaseOnly( Optional.of( previousIntegration ) ).doSecondPhase(); + } + } public interface PartialSetup { diff --git a/util/common/src/main/java/org/hibernate/search/util/common/impl/AbstractCloser.java b/util/common/src/main/java/org/hibernate/search/util/common/impl/AbstractCloser.java index 44fdf19ae18..cc32bd52e44 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/impl/AbstractCloser.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/impl/AbstractCloser.java @@ -8,6 +8,8 @@ import java.util.function.Function; +import org.hibernate.search.util.common.spi.ClosingOperator; + /** * A base class implementing the logic behind {@link Closer} and {@link SuppressingCloser}. * diff --git a/util/common/src/main/java/org/hibernate/search/util/common/impl/SuppressingCloser.java b/util/common/src/main/java/org/hibernate/search/util/common/impl/SuppressingCloser.java index b05c495c816..e804fc2efb4 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/impl/SuppressingCloser.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/impl/SuppressingCloser.java @@ -8,6 +8,8 @@ import java.util.function.Function; +import org.hibernate.search.util.common.spi.ClosingOperator; + /** * A helper for closing multiple resources and re-throwing a provided exception, * {@link Throwable#addSuppressed(Throwable) suppressing} any exceptions caught while closing. diff --git a/util/common/src/main/java/org/hibernate/search/util/common/impl/ClosingOperator.java b/util/common/src/main/java/org/hibernate/search/util/common/spi/ClosingOperator.java similarity index 88% rename from util/common/src/main/java/org/hibernate/search/util/common/impl/ClosingOperator.java rename to util/common/src/main/java/org/hibernate/search/util/common/spi/ClosingOperator.java index 558b617881d..0183cf1296b 100644 --- a/util/common/src/main/java/org/hibernate/search/util/common/impl/ClosingOperator.java +++ b/util/common/src/main/java/org/hibernate/search/util/common/spi/ClosingOperator.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.util.common.impl; +package org.hibernate.search.util.common.spi; @FunctionalInterface public interface ClosingOperator {