[Solr]Implicit RequestHander はどのように定義されているか

リファレンスによると、Implicit RequestHander は solrconfig.xml に設定を書かなくても使用できるリクエストハンドラである、と書かれています。
これらの Implicit RequestHander がどこでどのように定義されているのかを調べてみました。

Solr のソースでそれらしいものを探してみると solr/core/src/resources/ImplicitPlugins.json というファイルが見つかりました。

{
  "requestHandler": {
    "/update": {
      "useParams":"_UPDATE",
      "class": "solr.UpdateRequestHandler"
    },
    "/update/json": {
      "useParams":"_UPDATE_JSON",
      "class": "solr.UpdateRequestHandler",
      "invariants": {
        "update.contentType": "application/json"
      }
    },
    "/update/csv": {
      "useParams":"_UPDATE_CSV",
      "class": "solr.UpdateRequestHandler",
      "invariants": {
        "update.contentType": "application/csv"
      }
    },
    "/update/json/docs": {
      "useParams":"_UPDATE_JSON_DOCS",
      "class": "solr.UpdateRequestHandler",
      "invariants": {
        "update.contentType": "application/json",
        "json.command": "false"
      }
    },
(略)

これを読み込んでいるのが
solr/core/src/java/org/apache/solr/core/SolrCore.java
です。該当箇所は以下の通り。読み込んだ定義情報は getImplicitHandlers() で取得できます。

  private static final class ImplicitHolder {
    private ImplicitHolder() { }
    private static final List INSTANCE;
    static {
      @SuppressWarnings("unchecked")
      Map implicitPluginsInfo = (Map) Utils.fromJSONResource("ImplicitPlugins.json");
      @SuppressWarnings("unchecked")
      Map> requestHandlers = (Map>) implicitPluginsInfo.get(SolrRequestHandler.TYPE);
      List implicits = new ArrayList<>(requestHandlers.size());
      for (Map.Entry> entry : requestHandlers.entrySet()) {
        Map info = entry.getValue();
        info.put(CommonParams.NAME, entry.getKey());
        implicits.add(new PluginInfo(SolrRequestHandler.TYPE, info));
      }
      INSTANCE = Collections.unmodifiableList(implicits);
    }
  }
  public List getImplicitHandlers() {
    return ImplicitHolder.INSTANCE;
  }

RequestHanders クラスの初期化処理の中で、solrconfig.xml で定義されて RequestHander と implicit として定義されている RequestHander (SolrCore.getImplicitHanders()で取得)の両方が読み込まれて初期化されています。

  void initHandlersFromConfig(SolrConfig config) {
    List implicits = core.getImplicitHandlers();
    // use link map so we iterate in the same order
    Map infoMap= new LinkedHashMap<>();
    //deduping implicit and explicit requesthandlers
    for (PluginInfo info : implicits) infoMap.put(info.name,info);
    for (PluginInfo info : config.getPluginInfos(SolrRequestHandler.class.getName())) infoMap.put(info.name, info);
    ArrayList infos = new ArrayList<>(infoMap.values());
    List modifiedInfos = new ArrayList<>();
    for (PluginInfo info : infos) {
      modifiedInfos.add(applyInitParams(config, info));
    }
    handlers.init(Collections.emptyMap(),core, modifiedInfos);
    handlers.alias(handlers.getDefault(), "");
    if (log.isDebugEnabled()) {
      log.debug("Registered paths: {}", StrUtils.join(new ArrayList<>(handlers.keySet()), ','));
    }
    if (handlers.get("") == null && !handlers.alias("/select", "")) {
      if (handlers.get("") == null && !handlers.alias("standard", "")) {
        log.warn("no default request handler is registered (either '/select' or 'standard')");
      }
    }
  }

この時点で Implicit RequestHandler は solrconfig.xml で定義されているものと同等の扱いになっている訳です。

[Solr]solrconfig.xmlのinitParamsについて

solrconfig.xml には initParams というセクションがあります。これはリクエストハンドラのパラメータを指定するためのものです。リクエストハンドラのパラメータは基本的にそのハンドラの定義内で設定するものですが、initParams セクションには以下の役割があります。

  • いくつかのリクエストハンドラで共通するのパラメータのセットを1箇所で定義する
  • solrconfig.xml に定義の無い Implicit RequestHandler のパラメータを指定する

たとえば _default コンフィグセットの solrconfig.xml には以下の initParams 定義があります。

<initParams path="/update/**,/query,/select,/spell">
  <lst name="defaults">
    <str name="df">_text_</str>
  </lst>
</initParams>

path 属性で指定されているのは各リクエストハンドラのパスです。
アスタリスク2個のワイルドカードはそのパス以下のすべての階層を意味します。(アスタリスク1個なら1レベル下の階層まで)

initParams は name 属性を持つこともできます。たとえば以下の定義だとすると

<initParams path="/update/**,/query,/select,/spell" name="defaultParams">
  <lst name="defaults">
    <str name="df">_text_</str>
  </lst>
</initParams>

リクエストハンドラの定義において “defaultParams” という名前で参照できます。

<requestHandler name="/demo" class="DemoHandler" initParams="defaultParams"/>

各パラメータは defaults, appends, invariants のいずれかのオプションを指定できます。
それぞれのオプションの意味は以下の通りです。

  • defaults クエリ実行時に該当パラメータが指定されなかった場合のデフォルト値として使われる。
  • appends クエリ実行時に指定されたパラメータに追加して追加して使われる。
  • invariants クエリ実行時に必ず使われる。ユーザが指定したパラメータで上書きされることはない。

Amazonでのお買い物、、ちょっと待った🖐

子供が生まれて&在宅勤務するようになってから、日用品を購入する際にAmazonを利用する機会が倍増しています。
プライム会員特典の配送料無料につられて、気づくと買い物かごにどんどん商品が追加されています👼
特に月に一度あるタイムセールでは、日用品のまとめ買いや、欲しかった商品が安くなってないかを確認して虎視眈々と購入するタイミングを狙っています。

今回はAmazonでのお買い物の際に、私が利用している便利ツール「Keepa」をご紹介します。
このツールを使うとめちゃお得にお買い物できます(あやしい)

Keepa(キーパ)

Amazonで販売中の商品価格の推移をグラフで表示・確認できるサービスです。
Amazonでは、商品価格が上下することがよくあります。昨日までは安かったのに今日見たら値段が上がってる…😇とかもよくあると思います。そんなときにこのKeepaを利用すれば、今までの商品価格の推移を確認できるので、価格の上下するタイミングや最安値のタイミングを予測することができます。
https://keepa.com

Keepaを利用する

Keepaを利用するには、いつも使っているブラウザにプラグインをインストールする必要があります。
インストールは超かんたん。公式サイトからご利用中のブラウザに合ったプラグインをインストールしてください。
プラグインが有効化されていればOKです👌

基本的な使い方

Keepaの基本的な使い方は非常にシンプルです。
プラグインが有効化されている状態で、Amazonの価格推移を見たい商品のページの商品画像の下あたりにKeepaの価格推移グラフが表示されているかと思います📊
表示されていない場合は、プラグインが有効かどうか確認👀してみてください。


こんな感じでグラフが表示されます。
スクショは、子供が生まれるまでは縁もゆかりもなかった紙おむつ👶地味に4月からの値上がりなども確認できますね…
Keepaで価格推移を確認して、タイムセールなどの安くなるタイミングでストック買いしてます。


通常はAmazonで3,581円で販売されていますが、タイムセールで2,686円に値下がりしていることが確認できます。(4/25現在)
グラフを見ると、月末や通常のタイムセールで定期的に値下げをしているのが確認できるので、「もうそろそろ安くなるな😎」と、購入するスケジュールを立てやすくなります。

便利なトラッキング機能

Keepaには狙っている商品価格になったときに通知してくれるトラッキング機能もあります。
「商品のトラッキング」タブで通知させたい販売価格を入力して「トラッキング開始」をクリックします。
次の画面でメールアドレスを入力すると、トラッキング設定した販売価格になると入力したメールアドレス宛に通知されるようになります。
この設定をしておけば値引きされたタイミングでの買い忘れも防げるので、とても重宝している機能です。

便利なKeepaですが…

このように価格推移を確認できてAmazonでの買い物の際にとても助かるツールなのですが、Keepaで確認して安くなってたらすぐ買ってしまう、という罠に陥ることがあるので買い物の際には要注意です🙄

めいぼができて治った話

去年の暮れに左の上まぶたが妙に腫れて眼科に掛かりました。
雑菌が入ったんでしょうということで1週間分の薬を出してもらい、実際に1週間程度で腫れは治まったのですが、ぷくっとした大豆くらいの大きさの突起が残ってしまいました。特に痛くはないものの、触るとこりこりとした異物感があります。

再度眼科で診てもらったところ、霰粒腫という立派な病名をいただきました。俗にいうめいぼというやつです。涙の成分を分泌する脂腺が詰まってできるのだそうです。(ちなみに細菌感染で炎症が起こるのは麦粒腫(いわゆるものもらい)というそうです。)
いくつか薬はいただいたものの、霰粒腫は物理的に詰まりが取れないと治らないそうで、大人の場合は結構な確率で残るので、見た目がどうしても気になるなら手術で簡単に取れますよとのことでした。

左目の上がぽこっと飛び出ているのは確かに気になるけど手術は面倒だなあと思いつつ、しばらく様子を見ることにしました。
脂が詰まっているのだからあっためるのがいいらしいということで、仕事終わりに蒸気が出るアイマスクというのを当てて目を休めたり、お風呂でお湯をまぶたに当てるようにしてみたり。
その甲斐が有ったのか無かったのか、1ヶ月後くらいにはなんとか無くなってくれました。
触るとかすかに違和感が残っていますが、見た目は完全に元通りです。

人によっては霰粒腫は繰り返すこともあるようなので、折りに触れ目の周りを温めるというのは続けていこうと思っています。

強力の Callback プログラミング

こんにちは。開発担当のマットです。

最近、暇の時に、Unity というゲーム・エンジンで色々遊んでいます。
Unity で自由自在にゲームを作ることができ、武器を振り回して街を爆発させるゲームやゾンビー・ゲームやストラテジー・ゲームなど、色々作ったことはあります。

ところで、ゲームは割と複雑なものです。ビジュアルも、アニメーションも、効果音も色々ありまして、プログラムを正しく設計しないと、バグに繋がってしまいます。

従来の書き方

とても簡単な例でゲーム・プログラムの従来の書き方を紹介します。
例のゲームを、「マイ・ペット」と呼びましょう。このゲームにペットを育てることができます。
このゲームを作るには、データを持つクラスと、画面にペットを書き出すクラスは必要でしょう。

1. PetData クラス。
 このスクリプトの中に、ペットの色・サイズ・特徴など、全てのデータを記録しています。
2. DrawPet クラス
 このスクリプトは PetData のデータを使って、ペットを画面に描き出す(描画する)。

普通だったら、ゲームが開始されたら、DrawPet が PetData の中身を見て、ペットを画面に書き出します。

ところで、ゲームの中で「ピンク薬」という魔法なアイテムを使って、ペットの色が「ピンク」になった場合はどうしたらいいでしょう?DrawPet はペットを描き直さないといけませんが、データが変わったことにどうやって気づくでしょうか?

従来な方法は2つあります。

1. PetData の中のデータが変わる時、PetData は DrawPet のファンクションを呼び出す。(Function Call)
2. DrawPet は定期的に、PetData の中身が変わったかを確認する。(Polling)

ところで、どちらにも問題はあります。

Function Call の問題

データが変わった時に、DrawPet のファンクションを呼ぶことは大して悪くはないですが…
気をつけないと膨大してしまうプログラムの書き方です。

例えば、ゲームの中に「ペット詳細」の画面を追加しようと思った時、その「ペット詳細」画面を更新するファンクション・コールも必要になるでしょう。
それとも、色が変わった時、効果音を再生したい場合は?
アニメーションを実行したい場合は?

色のデータを変更するタイミングですべてのシステムにファンクション・コールをしないといけません。
そして、一つでも抜けたらバグ!

直感的ではありますが、問題ですね。

Polling の問題

確かに、DrawPet は PetData を確認したら、上記の問題は解決となります。

ただし、問題はタイミングとなります。DrawPet はいつ PetData を確認すればいいでしょうか?
一瞬で反応する仕組みを作りたければ、毎秒毎秒、何十回、何百回も繰り返しで PetData の中身を確認する必要が生まれてしまいます。

一つのペットなら問題ないかもしれませんが、ペットを増やそうと思ったら、その分の処理も増えます。
そして、DrawPetだけではなく、UI、効果音、アニメーションなど、それぞれのシステムは各々の PetData を連続的に確認する必要になりますね。

しかも、データはそれほど頻繁にかわらないので、無駄な処理ばっかり走っていますね。
ゲームがガタガタと遅くなってしまう恐れはあります。良くないですね。

Callback のチカラ

ここで紹介したいのは Callback の力です。

PetData は DrawPet などのファンクションを呼ぶことも良くないし、
DrawPet は PetData に毎回尋ねるのも、無駄な処理になりますので、
使えばいいのは Callback です。

PetData の方でファンクション参照先を登録できるようにすることです。
これを使って、DrawPet  はPetData に “RedrawPet” というファンクションを登録する。
DrawPet は “RedrawPet” のファンクションを実行するではなく、そのファンクションの参照先を PetData に登録するのだけです。

そして、PetData の方でデータが変わる時、PetData は登録された “RedrawPet” ファンクションを呼ぶことはできます。

このプログラミング方法を使うと、プログラマーはデータのクラスに、描画やUIや効果音やアニメーションのことを一切書くなくて大丈夫です。
それぞれのシステムはデータクラスにファンクションの参照先を登録をして、呼ばれるのを待つだけです。
「呼び返す」の感じですね。なので、Callback と言います。

ぜひ、次のプロジェクトで、Callbackプログラミングをお試しください。