All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.search.engine.common.impl.SearchIntegrationBuilder Maven / Gradle / Ivy

The newest version!
/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.search.engine.common.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.hibernate.search.engine.cfg.ConfigurationPropertySource;
import org.hibernate.search.engine.cfg.EngineSettings;
import org.hibernate.search.engine.cfg.spi.ConfigurationProperty;
import org.hibernate.search.engine.cfg.spi.EngineSpiSettings;
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.spi.SearchIntegrationPartialBuildState;
import org.hibernate.search.engine.common.timing.impl.DefaultTimingSource;
import org.hibernate.search.engine.common.timing.spi.TimingSource;
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.environment.thread.impl.ThreadPoolProviderImpl;
import org.hibernate.search.engine.environment.thread.spi.ThreadProvider;
import org.hibernate.search.engine.mapper.mapping.building.spi.BackendsInfo;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappedIndexManagerFactory;
import org.hibernate.search.engine.mapper.mapping.building.spi.Mapper;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingAbortedException;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingBuildContext;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingConfigurationCollector;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingInitiator;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingKey;
import org.hibernate.search.engine.mapper.mapping.building.spi.MappingPartialBuildState;
import org.hibernate.search.engine.mapper.model.spi.MappableTypeModel;
import org.hibernate.search.engine.mapper.model.spi.TypeMetadataContributorProvider;
import org.hibernate.search.engine.mapper.model.spi.TypeMetadataDiscoverer;
import org.hibernate.search.engine.reporting.FailureHandler;
import org.hibernate.search.engine.reporting.impl.EngineEventContextMessages;
import org.hibernate.search.engine.reporting.impl.FailSafeFailureHandlerWrapper;
import org.hibernate.search.engine.reporting.spi.RootFailureCollector;
import org.hibernate.search.util.common.AssertionFailure;
import org.hibernate.search.util.common.SearchException;
import org.hibernate.search.util.common.impl.SuppressingCloser;

public class SearchIntegrationBuilder implements SearchIntegration.Builder {

	private static final ConfigurationProperty> BACKGROUND_FAILURE_HANDLER =
			ConfigurationProperty.forKey( EngineSettings.Radicals.BACKGROUND_FAILURE_HANDLER )
					.asBeanReference( FailureHandler.class )
					.withDefault( EngineSettings.Defaults.BACKGROUND_FAILURE_HANDLER )
					.build();

	private static final ConfigurationProperty> THREAD_PROVIDER =
			ConfigurationProperty.forKey( EngineSpiSettings.Radicals.THREAD_PROVIDER )
					.asBeanReference( ThreadProvider.class )
					.withDefault( EngineSpiSettings.Defaults.THREAD_PROVIDER )
					.build();

	private final SearchIntegrationEnvironment environment;
	private final Optional previousIntegration;
	private final Map, MappingInitiator> mappingInitiators = new LinkedHashMap<>();

	private boolean frozen = false;

	public SearchIntegrationBuilder(SearchIntegrationEnvironment environment,
			Optional previousIntegration) {
		this.environment = environment;
		this.previousIntegration = previousIntegration;
		environment.propertyChecker().beforeBoot();
	}

	@Override
	public  SearchIntegration.Builder addMappingInitiator(
			MappingKey mappingKey, MappingInitiator initiator) {
		if ( frozen ) {
			throw new AssertionFailure(
					"Attempt to add a mapping initiator"
							+ " after Hibernate Search has started to build the mappings."
			);
		}

		MappingInitiator existing = mappingInitiators.putIfAbsent( mappingKey, initiator );

		if ( existing != null ) {
			throw new AssertionFailure(
					"Mapping key '" + mappingKey + "' has multiple initiators: '"
							+ existing + "', '" + initiator + "'."
			);
		}
		return this;
	}

	@Override
	public SearchIntegrationPartialBuildState prepareBuild() {
		ConfigurationPropertySource propertySource = environment.propertySource();
		BeanResolver beanResolver = environment.beanResolver();
		BeanHolder failureHandlerHolder = null;
		BeanHolder threadProviderHolder = null;
		IndexManagerBuildingStateHolder indexManagerBuildingStateHolder = null;
		// Use a LinkedHashMap for deterministic iteration
		List> mappingBuildingStates = new ArrayList<>();
		Map, MappingPartialBuildState> partiallyBuiltMappings = new HashMap<>();
		RootFailureCollector failureCollector = new RootFailureCollector( EngineEventContextMessages.INSTANCE.bootstrap() );
		boolean checkingRootFailures = false;
		EngineThreads engineThreads = null;
		TimingSource timingSource = null;

		try {
			frozen = true;

			failureHandlerHolder = BACKGROUND_FAILURE_HANDLER.getAndTransform( propertySource, beanResolver::resolve );
			// Wrap the failure handler to prevent it from throwing exceptions
			failureHandlerHolder = BeanHolder.of( new FailSafeFailureHandlerWrapper( failureHandlerHolder.get() ) )
					.withDependencyAutoClosing( failureHandlerHolder );
			FailureHandler failureHandler = failureHandlerHolder.get();

			threadProviderHolder = THREAD_PROVIDER.getAndTransform( propertySource, beanResolver::resolve );
			ThreadPoolProviderImpl threadPoolProvider = new ThreadPoolProviderImpl( threadProviderHolder );
			engineThreads = new EngineThreads( threadPoolProvider );
			timingSource = new DefaultTimingSource( engineThreads );

			RootBuildContext rootBuildContext = new RootBuildContext(
					propertySource,
					environment.classResolver(), environment.resourceResolver(), beanResolver,
					failureCollector, threadPoolProvider, failureHandler,
					engineThreads, timingSource
			);

			indexManagerBuildingStateHolder =
					new IndexManagerBuildingStateHolder( beanResolver, propertySource, rootBuildContext );

			// Step #1: collect configuration for all mappings
			for ( Map.Entry, MappingInitiator> entry : mappingInitiators.entrySet() ) {
				// We know the key and initiator have compatible types, see how they are put into the map
				@SuppressWarnings({ "rawtypes", "unchecked" })
				MappingBuildingState mappingBuildingState = new MappingBuildingState<>(
						rootBuildContext,
						(MappingKey) entry.getKey(), entry.getValue()
				);
				mappingBuildingStates.add( mappingBuildingState );
				mappingBuildingState.collect();
			}
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			// Step #2: create mappers
			for ( MappingBuildingState mappingBuildingState : mappingBuildingStates ) {
				mappingBuildingState.createMapper();
			}
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			// Step #3: determine indexed types and the necessary backends
			BackendsInfo backendsInfo = new BackendsInfo();
			for ( MappingBuildingState mappingBuildingState : mappingBuildingStates ) {
				mappingBuildingState.determineIndexedTypes( backendsInfo );
			}
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			// Step #4: create backends that will be necessary for mappers
			indexManagerBuildingStateHolder.createBackends( backendsInfo );
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			// Step #5: map indexed types and create the corresponding index managers
			MappedIndexManagerFactory mappedIndexManagerFactory =
					new MappedIndexManagerFactoryImpl( indexManagerBuildingStateHolder );
			for ( MappingBuildingState mappingBuildingState : mappingBuildingStates ) {
				mappingBuildingState.mapIndexedTypes( mappedIndexManagerFactory );
			}
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			// Step #6: create mappings
			for ( MappingBuildingState mappingBuildingState : mappingBuildingStates ) {
				mappingBuildingState.partiallyBuildAndAddTo( partiallyBuiltMappings );
			}
			checkingRootFailures = true;
			failureCollector.checkNoFailure();
			checkingRootFailures = false;

			return new SearchIntegrationPartialBuildStateImpl(
					environment.beanProvider(), beanResolver,
					failureHandlerHolder,
					threadPoolProvider,
					partiallyBuiltMappings,
					indexManagerBuildingStateHolder.getBackendNonStartedStates(),
					indexManagerBuildingStateHolder.getIndexManagersNonStartedStates(),
					environment.propertyChecker(),
					engineThreads, timingSource, previousIntegration
			);
		}
		catch (RuntimeException e) {
			RuntimeException rethrownException;
			if ( checkingRootFailures ) {
				// The exception was thrown by one of the failure checks above. No need for an additional check.
				rethrownException = e;
			}
			else {
				/*
				 * The exception was thrown by something other than the failure checks above
				 * (a mapper, a backend, ...).
				 * We should check that no failure was collected before.
				 */
				try {
					failureCollector.checkNoFailure();
					// No other failure, just rethrow the exception.
					rethrownException = e;
				}
				catch (SearchException e2) {
					/*
					 * At least one failure was collected, most likely before "e" was even thrown.
					 * Let's throw "e2" (which mentions prior failures), only mentioning "e" as a suppressed exception.
					 */
					rethrownException = e2;
					rethrownException.addSuppressed( e );
				}
			}

			SuppressingCloser closer = new SuppressingCloser( rethrownException );
			// Release the failure handler before aborting
			closer.push( failureHandlerHolder );
			// Close the mappers and mappings created so far before aborting
			closer.pushAll( MappingPartialBuildState::closeOnFailure, partiallyBuiltMappings.values() );
			closer.pushAll( MappingBuildingState::closeOnFailure, mappingBuildingStates );
			// Close the resources contained in the index manager building state before aborting
			closer.pushAll( holder -> holder.closeOnFailure( closer ), indexManagerBuildingStateHolder );
			// Close environment resources before aborting
			closer.pushAll( BeanHolder::close, threadProviderHolder );
			closer.pushAll( SearchIntegrationEnvironment::close, environment );
			closer.push( EngineThreads::onStop, engineThreads );
			closer.push( TimingSource::stop, timingSource );

			throw rethrownException;
		}
	}

	private static class MappingBuildingState {
		private final MappingBuildContext buildContext;

		private final MappingKey mappingKey;
		private final MappingInitiator mappingInitiator;

		private TypeMetadataContributorProvider metadataContributorProvider;

		private Mapper mapper; // Initially null, set in createMapper()

		MappingBuildingState(RootBuildContext rootBuildContext,
				MappingKey mappingKey, MappingInitiator mappingInitiator) {
			this.mappingKey = mappingKey;
			this.buildContext = new MappingBuildContextImpl( rootBuildContext, mappingKey );
			this.mappingInitiator = mappingInitiator;
		}

		void collect() {
			TypeMetadataContributorProvider.Builder builder = TypeMetadataContributorProvider.builder();
			mappingInitiator.configure( buildContext, new MappingConfigurationCollectorImpl( builder ) );
			metadataContributorProvider = builder.build();
		}

		void createMapper() {
			mapper = mappingInitiator.createMapper( buildContext, metadataContributorProvider );
		}

		void determineIndexedTypes(BackendsInfo backendsInfo) {
			mapper.prepareMappedTypes( backendsInfo );
		}

		void mapIndexedTypes(MappedIndexManagerFactory indexManagerFactory) {
			mapper.mapTypes( indexManagerFactory );
		}

		void partiallyBuildAndAddTo(Map, MappingPartialBuildState> mappings) {
			try {
				PBM partiallyBuiltMapping = mapper.prepareBuild();
				mappings.put( mappingKey, partiallyBuiltMapping );
			}
			catch (MappingAbortedException e) {
				e.collectSilentlyAndCheck( buildContext.failureCollector() );
			}
		}

		public void closeOnFailure() {
			if ( mapper != null ) {
				mapper.closeOnFailure();
			}
		}

		private class MappingConfigurationCollectorImpl implements MappingConfigurationCollector {
			private final TypeMetadataContributorProvider.Builder builder;

			private MappingConfigurationCollectorImpl(TypeMetadataContributorProvider.Builder builder) {
				this.builder = builder;
			}

			@Override
			public void collectContributor(MappableTypeModel typeModel, C contributor) {
				builder.contributor( typeModel, contributor );
			}

			@Override
			public void collectDiscoverer(TypeMetadataDiscoverer metadataDiscoverer) {
				builder.discoverer( metadataDiscoverer );
			}
		}

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy