// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.search.federation.sourceref; import com.yahoo.component.ComponentId; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.processing.request.Properties; import com.yahoo.search.searchchain.model.federation.FederationOptions; import java.util.Collections; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * Resolves (source, provider) component specifications to a search chain invocation spec. * The provider component specification is given by the entry in the queryMap with key * 'source.<source-name>.provider'. * *

* The diagram shows the relationship between source, provider and the result: * (source is used to select row, provider is used to select column.) * Provider id = null is used for regular search chains. *

* *
 *                   Provider id
 *                 null
 *                |----+---+---+---|
 *                | o  |   |   |   |
 *                |----+---+---+---|
 * Source id      |    | o | o |   |
 *                |----+---+---+---|
 *                |    |   |   | o |
 *                |----+---+---+---|
 *
 *                    o: SearchChainInvocationSpec
 * 
* * @author Tony Vaagenes */ public class SearchChainResolver { private final ComponentRegistry targets; private final SortedSet defaultTargets; public static class Builder { public interface InvocationSpecFactory { SearchChainInvocationSpec create(ComponentId searchChainId, FederationOptions federationOptions, List schemas); } private class DefaultInvocationSpecFactory implements InvocationSpecFactory { public SearchChainInvocationSpec create(ComponentId searchChainId, FederationOptions federationOptions, List schemas) { return new SearchChainInvocationSpec(searchChainId, federationOptions, schemas); } } private final SortedSet defaultTargets = new TreeSet<>(); private final ComponentRegistry targets = new ComponentRegistry<>() { @Override public void freeze() { allComponents().forEach(Target::freeze); super.freeze(); } }; public Builder addSearchChain(ComponentId searchChainId, FederationOptions federationOptions) { return addSearchChain(searchChainId, federationOptions, List.of()); } public Builder addSearchChain(ComponentId searchChainId, List schemas) { return addSearchChain(searchChainId, new FederationOptions(), schemas); } public Builder addSearchChain(ComponentId searchChainId, FederationOptions federationOptions, List schemas) { addSearchChain(new SearchChainInvocationSpec(searchChainId, federationOptions, schemas)); return this; } private Builder addSearchChain(SearchChainInvocationSpec invocationSpec) { return registerTarget(new SingleTarget(invocationSpec.searchChainId, invocationSpec, false)); } public Builder addSearchChain(ComponentId id, SearchChainInvocationSpec invocationSpec) { return registerTarget(new SingleTarget(id, invocationSpec, false)); } private Builder registerTarget(SingleTarget singleTarget) { targets.register(singleTarget.getId(), singleTarget); if (singleTarget.useByDefault()) { defaultTargets.add(singleTarget); } return this; } public Builder addSourceForProvider(ComponentId sourceId, ComponentId providerId, ComponentId searchChainId, boolean isDefaultProviderForSource, FederationOptions federationOptions, List schemas) { var searchChainInvocationSpec = new SearchChainInvocationSpec(searchChainId, sourceId, providerId, federationOptions, schemas); SourcesTarget sourcesTarget = getOrRegisterSourceTarget(sourceId); sourcesTarget.addSource(providerId, searchChainInvocationSpec, isDefaultProviderForSource); registerTarget(new SingleTarget(searchChainId, searchChainInvocationSpec, true)); return this; } private SourcesTarget getOrRegisterSourceTarget(ComponentId sourceId) { Target sourcesTarget = targets.getComponent(sourceId); if (sourcesTarget == null) { targets.register(sourceId, new SourcesTarget(sourceId)); return getOrRegisterSourceTarget(sourceId); } else if (sourcesTarget instanceof SourcesTarget) { return (SourcesTarget) sourcesTarget; } else { throw new IllegalStateException("Expected " + sourceId + " to be a source."); } } public void useTargetByDefault(String targetId) { Target target = targets.getComponent(targetId); assert target != null : "Target not added yet."; defaultTargets.add(target); } public SearchChainResolver build() { targets.freeze(); return new SearchChainResolver(targets, defaultTargets); } } private SearchChainResolver(ComponentRegistry targets, SortedSet defaultTargets) { this.targets = targets; this.defaultTargets = Collections.unmodifiableSortedSet(defaultTargets); } public SearchChainInvocationSpec resolve(ComponentSpecification sourceRef, Properties sourceToProviderMap) throws UnresolvedSearchChainException { Target target = resolveTarget(sourceRef); return target.responsibleSearchChain(sourceToProviderMap); } private Target resolveTarget(ComponentSpecification sourceRef) throws UnresolvedSearchChainException { Target target = targets.getComponent(sourceRef); if (target == null) { throw UnresolvedSourceRefException.createForMissingSourceRef(sourceRef); } return target; } public SortedSet allTopLevelTargets() { SortedSet topLevelTargets = new TreeSet<>(); for (Target target : targets.allComponents()) { if (!target.isDerived) { topLevelTargets.add(target); } } return topLevelTargets; } public SortedSet defaultTargets() { return defaultTargets; } }