// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.search; import com.yahoo.component.ComponentId; import com.yahoo.processing.Processor; import com.yahoo.processing.Response; import com.yahoo.search.searchchain.Execution; import java.util.logging.Logger; /** * Superclass of all {@link com.yahoo.component.Component Components} which produces Results in response to * Queries by calling the {@link #search search} method. *

* Searchers are participants in chain of responsibility {@link com.yahoo.search.searchchain.SearchChain search chains} * where they passes the Queries downwards by synchroneously calling the next Searcher in the chain, and returns the * Results back up as the response. *

* Any Searcher may *

* *

...or some combination of the above of course. Note that as Searchers work synchronously, any information can be * retained on the stack in the Searcher from the Query is received until the Result is returned simply by declaring * variables for the data in the search method (or whatever it calls), and for the same reason workflows are * implemented as Java code. However, searchers are executed by many threads, for different Queries, in parallell, so * any mutable data shared between queries (and hence stored as instance members must be accessed multithread safely. * In many cases, shared data can simply be instantiated in the constructor and used in read-only mode afterwards *

* Searcher lifecycle: A searcher has a simple life-cycle: * *

* * @author bratseth */ public abstract class Searcher extends Processor { // Note to developers: If you think you should add something here you are probably wrong // Create a subclass containing the new method instead. private final Logger logger = Logger.getLogger(getClass().getName()); public Searcher() {} /** Creates a searcher from an id */ public Searcher(ComponentId id) { super(); initId(id); } /** * Override this to implement your searcher. *

* Searcher implementation subclasses will, depending on their type of logic, do one of the following: *

*

* Hits come in two kinds - concrete hits are actual * content of the kind requested by the user, meta hits are * hits which provides information about the collection of hits, * on the query, the service and so on. *

* The query specifies a window into a larger result list that must be returned from the searcher * through hits and offset; * Searchers which returns list of hits in the top level in the result * must return at least hits number of hits (or if impossible; all that are available), * starting at the given offset. * In addition, searchers are allowed to return * any number of meta hits (although this number is expected to be low). * For hits contained in nested hit groups, the concept of a window defined by hits and offset * is not well defined and does not apply. *

* Error handling in searchers: *

* * @param query the query * @return the result of making this query */ public abstract Result search(Query query,Execution execution); /** Use the search method in Searcher processors. This forwards to it. */ @Override public final Response process(com.yahoo.processing.Request request, com.yahoo.processing.execution.Execution execution) { return search((Query)request, (Execution)execution); } /** * Fill hit properties with data using the given summary class. * Calling this on already filled results has no cost. *

* This needs to be overridden by federating searchers to contact search sources again by * propagating the fill call down through the search chain, and by source searchers * which talks to fill capable backends to request the data to be filled. Other searchers do * not need to override this. * * @param result the result to fill * @param summaryClass the name of the collection of fields to fetch the values of, or null to use the default */ public void fill(Result result, String summaryClass, Execution execution) { execution.fill(result, summaryClass); } /** * Fills the result if it is not already filled for the given summary class. * See the fill method. */ public final void ensureFilled(Result result, String summaryClass, Execution execution) { if (summaryClass == null) summaryClass = result.getQuery().getPresentation().getSummary(); if ( ! result.isFilled(summaryClass)) { fill(result, summaryClass, execution); } else { int fillRejectTraceAt = 3; if (result.getQuery().getTrace().getLevel() >= fillRejectTraceAt) result.getQuery().trace("Ignoring fill(" + summaryClass + "): " + ( result.hits().getFilled() == null ? "Hits are unfillable" : "Hits already filled" ) + ": result.hits().getFilled()=" + result.hits().getFilled(), fillRejectTraceAt); } } /** Returns a logger unique for the instance subclass */ protected Logger getLogger() { return logger; } /** Returns "searcher 'getId()'" */ @Override public String toString() { return "searcher '" + getIdString() + "'"; } }