class IndexingService extends LifecycleAdapter implements IndexingUpdateService, IndexingProvidersService {

    @Override
    public void start()
    {
        state = State.STARTING;

        // During recovery there could have been dropped indexes. Dropping an index means also updating the counts store,
        // which is problematic during recovery. So instead drop those indexes here, after recovery completed.
        performRecoveredIndexDropActions();

        // Recovery will not do refresh (update read views) while applying recovered transactions and instead
        // do it at one point after recovery... i.e. here
        indexMapRef.indexMapSnapshot().forEachIndexProxy( indexProxyOperation( "refresh", IndexProxy::refresh ) );

        final MutableLongObjectMap<StoreIndexDescriptor> rebuildingDescriptors = new LongObjectHashMap<>();
        indexMapRef.modify( indexMap ->
        {
            Map<InternalIndexState, List<IndexLogRecord>> indexStates = new EnumMap<>( InternalIndexState.class );
            Map<IndexProviderDescriptor,List<IndexLogRecord>> indexProviders = new HashMap<>();

            // Find all indexes that are not already online, do not require rebuilding, and create them
            indexMap.forEachIndexProxy( ( indexId, proxy ) ->
            {
                InternalIndexState state = proxy.getState();
                StoreIndexDescriptor descriptor = proxy.getDescriptor();
                IndexProviderDescriptor providerDescriptor = descriptor.providerDescriptor();
                IndexLogRecord indexLogRecord = new IndexLogRecord( descriptor );
                indexStates.computeIfAbsent( state, internalIndexState -> new ArrayList<>() )
                        .add( indexLogRecord );
                indexProviders.computeIfAbsent( providerDescriptor, indexProviderDescriptor -> new ArrayList<>() )
                        .add( indexLogRecord );
                internalLog.debug( indexStateInfo( "start", state, descriptor ) );
                switch ( state )
                {
                case ONLINE:
                    // Don't do anything, index is ok.
                    break;
                case POPULATING:
                    // Remember for rebuilding
                    rebuildingDescriptors.put( indexId, descriptor );
                    break;
                case FAILED:
                    // Don't do anything, the user needs to drop the index and re-create
                    break;
                default:
                    throw new IllegalStateException( "Unknown state: " + state );
                }
            } );
            logIndexStateSummary( "start", indexStates );
            logIndexProviderSummary( indexProviders );

            dontRebuildIndexesInReadOnlyMode( rebuildingDescriptors );
            // Drop placeholder proxies for indexes that need to be rebuilt
            dropRecoveringIndexes( indexMap, rebuildingDescriptors.keySet() );
            // Rebuild indexes by recreating and repopulating them
            populateIndexesOfAllTypes( rebuildingDescriptors, indexMap );

            return indexMap;
        } );

        samplingController.recoverIndexSamples();
        samplingController.start();

        // So at this point we've started population of indexes that needs to be rebuilt in the background.
        // Indexes backing uniqueness constraints are normally built within the transaction creating the constraint
        // and so we shouldn't leave such indexes in a populating state after recovery.
        // This is why we now go and wait for those indexes to be fully populated.
        rebuildingDescriptors.forEachKeyValue( ( indexId, descriptor ) ->
                {
                    if ( descriptor.type() != IndexDescriptor.Type.UNIQUE )
                    {
                        // It's not a uniqueness constraint, so don't wait for it to be rebuilt
                        return;
                    }

                    IndexProxy proxy;
                    try
                    {
                        proxy = getIndexProxy( indexId );
                    }
                    catch ( IndexNotFoundKernelException e )
                    {
                        throw new IllegalStateException(
                                "What? This index was seen during recovery just now, why isn't it available now?", e );
                    }

                    if ( proxy.getDescriptor().getOwningConstraint() == null )
                    {
                        // Even though this is an index backing a uniqueness constraint, the uniqueness constraint wasn't created
                        // so there's no gain in waiting for this index.
                        return;
                    }

                    monitor.awaitingPopulationOfRecoveredIndex( descriptor );
                    awaitOnline( proxy );
                } );

        state = State.RUNNING;
    }

      IndexingService( IndexProxyCreator indexProxyCreator,
            IndexProviderMap providerMap,
            IndexMapReference indexMapRef,
            IndexStoreView storeView,
            Iterable<SchemaRule> schemaRules,
            IndexSamplingController samplingController,
            TokenNameLookup tokenNameLookup,
            JobScheduler scheduler,
            SchemaState schemaState,
            MultiPopulatorFactory multiPopulatorFactory,
            LogProvider internalLogProvider,
            LogProvider userLogProvider,
            Monitor monitor,
            boolean readOnly );

}