Apache Solrソースコードリーディング SearchHandler/SearchComponent

Solrのソースコードを読んでいる。
拡張ポイントが多いので、読むと結構工夫できそうなところがあって面白い。

記録に残すため、少しずつ書いてみようかと思う。

まず、エントリーポイントに近いSearchHandlerと、SearchComponent


SearchHandlerの前に、RequestHandlerとはなんぞやという話から。

solrconfig.xmlに設定されているDataImportHandlerとか、SearchHandlerなどは、/solr/以下のURLと対応づけられ、データを取り込むなり、検索するなりの機能が実装されている。基本的にRequestHandlerBaseというクラスを継承して実装される。

そのRequestHandlerBaseのサブクラスの一つに、検索を行う機能が実装されているo.a.s.handler.SearchHandlerがある。

(o.a.sというのはorg.apache.solrのパッケージ名のこと)

SearchHandlerは、検索結果を処理するために複数のSearchComponentを持っている。

その設定の部分がおそらく以下

public class SearchHandler extends RequestHandlerBase implements SolrCoreAware
{
  static final String INIT_COMPONENTS = "components";
  static final String INIT_FIRST_COMPONENTS = "first-components";
  static final String INIT_LAST_COMPONENTS = "last-components";

中略

  @SuppressWarnings("unchecked")
  public void inform(SolrCore core)
  {
    Object declaredComponents = initArgs.get(INIT_COMPONENTS);
    List<String> first = (List<String>) initArgs.get(INIT_FIRST_COMPONENTS);
    List<String> last  = (List<String>) initArgs.get(INIT_LAST_COMPONENTS);

    List<String> list = null;
    boolean makeDebugLast = true;
    if( declaredComponents == null ) {
      // Use the default component list
      list = getDefaultComponents();

      if( first != null ) {
        List<String> clist = first;
        clist.addAll( list );
        list = clist;
      }

      if( last != null ) {
        list.addAll( last );
      }
    }
    else {
      list = (List<String>)declaredComponents;
      if( first != null || last != null ) {
        throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
            "First/Last components only valid if you do not declare 'components'");
      }
      makeDebugLast = false;
    }

initArgsはRequestHandlerBaseのフィールドで、型はNamedList。おそらくここにsolrconfig.xmlで定義された内容が、XMLのname属性をキーにして格納される。だから、declaredComponentsにはname="components"以下の内容のListが入るのだろう。

クラス図チックに書くと、こんな感じ。

RequestHandlerBase---<|----SearchHandler----SearchComponent

RequestHandlerは、メイン処理をhandleRequestBodyメソッドに記述する。SearchHandlerは検索が役割だから当然検索をするが、実は検索処理自体はSearchHandlerには含まれない。SearchHandlerは定義されたSearchComponentを順に処理させるだけ。定義されたComponentの中に検索処理を行うQueryComponentが含まれることによって検索を行うことができる。

さっきのSearchHandlerのinformメソッドは、以下のように続く

    // Build the component list
    components = new ArrayList<SearchComponent>( list.size() );
    DebugComponent dbgCmp = null;
    for(String c : list){
      SearchComponent comp = core.getSearchComponent( c );
      if (comp instanceof DebugComponent && makeDebugLast == true){
        dbgCmp = (DebugComponent) comp;
      } else {
        components.add(comp);
        log.info("Adding  component:"+comp);
      }
    }

componentsにSearchComponentのリストを作っている

で、handleRequestBodyの中では、以下のように全コンポーネントの処理を順に行っている。

      for( SearchComponent c : components ) {
        c.prepare(rb);
      }
        for( SearchComponent c : components ) {
          c.process(rb);
        }

rbというのはResponseBuilderで、レスポンス内容を組み立てるために利用されるクラスだ。各ComponentはそこまでのResponseBuilderの内容を参考に処理を行い、結果を追加する。

たぶん、最後にhttpレスポンスを返すときにはこのクラスの内容をXMLなり、jsonなりにシリアライズして返しているのだろう。

component.processを呼び出すところは、rb.shards ==null,つまりdistributed searchをしているかどうかで処理を分岐しているんだけど、分散検索の方はちょっと呼んだだけじゃなにがどうなってんのか良くわかんなかったけど、componentのdistributedProcess,handleResponses,finishStageといったメソッドを呼び出してる。多分distributed searchに対応したsearchComponentを実装するにはこれらのメソッドを実装する必要があるんだと思う。

これを僕が調べたのは、特定キーワードの場合に特定のコンテンツを一番上に表示させたり検索結果から除外するために利用されるQueryElevationComponentを調べたかったから。これもSearchComponentの一種。


次はそのQueryElevationComponentを読んでみよう。