Magnolia Tech

いつもコードのことばかり考えている人のために。

The Central RepositoryのArtifactをRESTなAPI経由で検索する

前回に続いて、The Central Repositoryで検索する話です。

前回のエントリ blog.magnolia.tech

今度はREST APIを使います。

REST APIでArtifactを検索する

The Central RepositoryにはGUI経由の検索だけでなく、REST API経由での検索機能も提供されています。

例えば、junitを検索するURLは以下のようになります。

http://search.maven.org/solrsearch/select?q=junit&rows=20&wt=json&start=0

curlコマンドに上記のURLを引数として渡すと、結果がJSONで返って来る様子が分かります。

{"responseHeader":{"status":0,"QTime":0,"params":{"spellcheck":"true","fl":"id,g,a,latestVersion,p,ec,repositoryId,text,timestamp,versionCount","sort":"score desc,timestamp desc,g asc,a asc","indent":"off","q":"junit","qf":"text^20 g^5 ....................(長くなるので、以降省略)

APIの解説は以下のドキュメントに一応記載されています。個々のパラメータの詳細までは説明されていませんが、検索機能はApache Solrで構築されているので、Solrを使った検索経験が有ればある程度分かると思います(qとかrowsとかstart)。

http://search.maven.org/#api

ただ、ちょっと分かりづらいので、rubyでパラメータを組み立てるようなコードを用意しました。レスポンスもrubyのハッシュに変換してダンプします。各パラメータの値を変えながら、結果がどう変化するのか試してみてください。

require 'uri'
require 'open-uri'
require 'json'
require 'pp'

params = {
    :q      => "junit",
    :wt     => "json",
    :rows   => 20,
    :start  => 0,
}

base_url = "http://search.maven.org/solrsearch/select?"

url = base_url + URI.encode_www_form(params)
res = OpenURI.open_uri( url ).read

puts url
pp JSON.load( res )

以下に、パラメータの内容をまとめてみました。

クエリパラメータの作り方

主なパラメータは以下の通りです。

  • q

    クエリの内容を指定します。

    • キーワード検索

      GUIからの検索と同じようにGroupIdか、ArtifactIdのどちらかにマッチするArtifactを検索するキーワード検索です。q={keyword}というようにキーワードを単体でセットします。例えばjunitを検索したい場合は、q=junitになります。ドットや、ハイフンで分割した単語単位で完全一致するものを特定しているようです(例えばyoda-timeyoda-はマッチするが、yoda-tiではマッチしない)。

    • GroupId検索

      GroupIdのみを検索対象に指定できます。q=g:"org.scala-lang"と指定すれば、GroupIdがorg.scala-langのArtifactのみが抽出されます。

    • ArtifactId検索

      GroupId検索と同様にArtifactのみを検索対象に指定できます。q=a:"scala-dist"と指定すれば、ArtifactIdがscala-distのArtifactのみが抽出されます。同じArtifact名でもforkされたものが複数のGroupIdで登録されている場合が有りますので、どれが目的のパッケージなのかは、結局はそれぞれのpomファイルのメタデータや、GroupId等から識別する必要が有ります。

    • GroupIdとArtifactIdのAND条件検索

      GroupIdと、ArtifactIdのAND条件検索も可能です。q=g:"org.scala-lang"+AND+a:"scala-dist"と指定すれば、両方の条件を満たすArtifactが抽出されます。Artifactは、GroupIdとArtifactIdで一意に特定されるので、通常はこの指定でマッチするものが有れば結果は一つしか返却されませんが、後述するcore=gav指定で、バージョンの一覧を取得します。

  • rows

    一度に取得する検索結果の数を指定します。省略した場合のデフォルト値は10です。最大値はAPIガイドに明記されていませんが、391以上で500エラーが出ました。いずれにしてもあまり大きな数値を指定すると1クエリあたりの負荷が大きくなるので、控えた方が良いでしょう。

  • start

    当然実際の検索結果がrowsで指定した個数を超える場合があり得ます。そんなときは、このstartパラメータを指定することで、検索結果の何個目以降を取得する、という指定ができます。省略した場合のデフォルトは0です。

  • wt

    検索結果をJSON形式で受け取るか、XML形式で受け取るか指定します。省略するとJSONになるので、特に指定する必要は無いでしょう。

  • core

    qにGroupIdと、ArtifactIdを両方指定した際に、合わせてcore=gavをセットすると、バージョンの一覧を取得します。

上記のパラメータをREST APIのお作法に則り組み合わせたURLを使って検索を実行します。APIの組み合わせ例は公式ドキュメントを確認してみて下さい。

検索結果の取得

検索の結果、マッチするものが有れば、JSONオブジェクトのキーをresponse -> numFoundとたどると、Artifactの数が分かります。指定したrowsを超える数が返って来ていたら、startパラメータにrowsの数を足して繰り返しクエリを発行します。

実際のArtifactの情報はresponse -> docsの配列に格納されています。

  • g -> GropuId
  • a -> ArtifactId
  • latestVersion -> 最新バージョン
  • ec -> 登録されているファイルの拡張子のリスト(.jar, .pom, .javadoc..)
  • versionCount -> 登録されているバージョンの数

バージョン一覧の取得

Artifactが一意に特定されるように、qパラメータにGroupIdと、ArtifactIdの両方を指定し、coreパラメータにgavを指定すると、バージョンの一覧を取得します。Artifactの情報にvが追加され、バージョン情報が追加されます。

例えば、先ほどのrubyのコードのparamsの中身を以下のように書き換えるとscalaのバージョンの一覧が取得できます。

params = {
    :wt     => "json",
    :rows   => 20,
    :start  => 0,
    :core => "gav",
    :q      => %{g:"org.scala-lang" AND a:"scala-dist"},
}

その他

その他、クラス名や、ファイルのSHA-1、tagsでの検索など有りますが、おそらく使うことはあまり無いと思いますので割愛します(APIガイドではtagsによりscalaのArtifactの検索性が上がるように見えますが、実際には全然登録されていないので、機能しません)。

ファイルのダウンロード

GroupId、ArtifactId、Versionと、ファイル名が分かればファイルをダウンロードできます。パス名は以下の通りです。

http://search.maven.org/remotecontent?filepath={GroupIDのドットをスラッシュに変換したもの}/{ArtifactID}/{version}/{ArtifactID}-{version}.{拡張子 or ファイル名}

Artifact名にドットが入っているものも有るので、ArtifactIDもドットをスラッシュに変換した方が良さそうです

例えば、mockito-core ver.2.8.47のpomファイルはこのようなパスになります(XMLなのでブラウザで開くとそのまま中身が表示されます)。

http://search.maven.org/remotecontent?filepath=org/mockito/mockito-core/2.8.47/mockito-core-2.8.47.pom

jarファイルだとこうなり、ブラウザでアクセスするとダウンロードが始まります。

http://search.maven.org/remotecontent?filepath=org/mockito/mockito-core/2.8.47/mockito-core-2.8.47.jar

javadoc.jarだとこうなります。

http://search.maven.org/remotecontent?filepath=org/mockito/mockito-core/2.8.47/mockito-core-2.8.47-javadoc.jar

検索用のAPIと組み合わせることで、例えば依存関係にあるjarの最新バージョンを特定して一気にダウンロードする、なんてことができるようになります。

応用編 - ScalaのArtifactのバージョン一覧を取得する

単純な検索をするだけなら、GUIからの検索と変わりません。

しかし、例えばScalaのArtifactは末尾にメジャーバージョンを付与するルールとなっていて(Scala 2.12用のScalaTestのArtifactIdはscalatest_2.12となります)、かつメジャーバージョンごとにArtifact自体のバージョニングが行われ、メジャーバージョンをどこまでサポートするかは完全に各Artifactのメンテナンスポリシーに依ります。そうするとどのArtifactがどのメジャーバージョンをサポートしていて、それぞれどのバージョンがリリースされているか?ということをきちんと調べないといけないので、なかなか大変です。

こんな時、REST APIが有れば、メジャーバージョン毎のバージョン一覧を取得することができます。

コードが長くなったのでgistに貼り付けていますが、取得したGroupIDをキーにArtifactの一覧を取得して、あとはメジャーバージョンごとにバージョン一覧の取得を行っています。

collect_scala_artifact_version.rb

コード例ではメジャーバージョンをキーにしたハッシュに格納して終わっていますが、例えばバージョン毎にサポートしているScalaのメジャーバージョン一覧を作るのも簡単にできます。

mvnrepositoryについて

The Central Repositoryの検索サイトと、似たようなサイトに「mvnrepository.com」が有ります。

Maven Repository: Search/Browse/Explore

こちらはJVM系パッケージのリポジトリサイトの横断検索を行うサイトです。titleに「Maven Repository: Search/Browse/Explore」と出てきて公式っぽいですが、個人運営のサイトだそうです。

REST APIは公開されていませんが、検索結果の見やすさ等は工夫されていますね。

ただ、各リポジトリサイトはクロールを禁止していることが多いので、どうやってインデックスを作っているのか謎です。

おわりに

言語ごとにパッケージエコシステムがどこまで面倒を見るか?という所が違っていて、Goのように「各自GitHubリポジトリを探して下さい、以上!」というものから、Perlのように唯一のリポジトリ(CPAN)がパッケージ管理、検索、ドキュメント閲覧から、パッケージ自体の品質保証(メタデータの充足性、様々な環境でのテスト実行まで)までやってしまう至れり尽くせりの言語まで千差万別です。

JVM系は比較的淡泊な方で、結局はGitHubリポジトリを見れば最新の情報は揃っているので、The Central Repositoryの検索機能は「リリースされたバージョン一覧の取得」以上の使い方は無い気もしますが、APIは使い方によっては色々と活用方法が有りますね(依存しているライブラリの公式サイトを全部リストアップするとか)。

ぜひ使ってみてください。

The Central RepositoryでArtifactを検索する

The Central Repositoryとは?

The Central RepositoryとはSonatype社が運営するJVM系言語のためのパブリックモジュールリポジトリです。Apache Mavenや、sbtといったビルドツールのデフォルトの参照先になっているので、最もよく使われているJVMリポジトリといえるでしょう。

f:id:magnoliak:20170628001615p:plain

JVM系のモジュールパッケージは、Mavenの慣習にならってArtifact(歴史的価値の有る遺物、工芸品という意)と呼ばれますので、以降Artifactと記載します。

(Bintrayが運営するjcenterなどもメジャーなリポジトリですが、こちらでは一般的なpackageという記載になっていますね)

GUIでArtifactを検索する

The Central Repositoryは単なるリポジトリアーカイブだけでなく、Artifactの検索機能も提供しています。

The Central Repository Search Engine

f:id:magnoliak:20170628001645p:plain

キーワードを入力してSEARCHボタンを押下すると、GroupId(パッケージ名、大抵はドメイン名の逆順)か、ArtifactId(プロジェクト名)にマッチするものの一覧が表示されます。

例えば、junitを検索した結果は以下のように表示されます。

f:id:magnoliak:20170628001816p:plain

「latestVersion」という列には最新バージョンと、The Central Repositoryに登録されているバージョンの個数が括弧付きで表示されます。junitの検索例では、GroupId、ArtifactIdがともに「junit」となっている7行目を見て下さい。最新バージョンが「4.12」、登録されているバージョンが全部で24個となっていることが分かるかと思います。

Artifactはforkしたプロジェクトが別のGroupIdで公開されていることが多いので、Artifact名だけだと目的のパッケージがオリジナルなのか、forkされたものなのか分かりづらいですが…

最新バージョンのバージョンナンバーをクリックするとpomファイルの中身(XML)が表示され、ウェブサイトの情報やライセンス、作者の情報などが確認できます。素のXMLがそのまま表示されるので、読み取りづらいですが、入手可能な情報はこれが全てなので、ここで全て確認することができます。残念ながらpomファイルの中身は検索対象にはなっていません。

また、左下にはMavenやsbtといった各種JVM系ビルドツールでArtifactへの依存関係の指定方法が例示されていますので、こちらをビルドの設定ファイルへコピペすれば依存関係を定義できるようになっています。地味に嬉しい機能です。

f:id:magnoliak:20170628004200p:plain

バージョン個数をクリックすると、入手可能なバージョンの一覧が表示されます。

f:id:magnoliak:20170628001728p:plain

検索対象がほぼほぼgroupIDとartifactIDに限られていること、命名規約的にPerlのモジュール名のような一般名詞の組み合わせでできているわけではないので、検索機能を使って未知のモジュールを探す、というより既知のモジュールのバージョン一覧を取得する方が主な使い方になるでしょう(最新バージョンの情報が欲しいだけならGitHubを参照した方が早いし、javadocの中身を直接表示する機能も無いので…)。

次回はREST APIを使って検索する話をします。

「エラスティックリーダーシップ」を読んだ

あまりこの手のリーダー論みたいなものを本で読むのが苦手なので、めったに買わないけど、周りで評判が良かったので買いました。

「サバイバルモード中毒」とか、「コミットメント言語」とかキャッチーな言葉で、世の中のプロジェクトで起こりがちな問題をきちんと定義していて、分かりやすいですね。常に、自分たちが「こんな状態になっていないか?」と確認するためには、非常に良いやり方です。

解決に向けたアドバイスは色々と書かれてはいますが、それは結局は各プロジェクトの状況次第なところも有って絶対的な正解は無いと思いますが、まずは自分たちの立ち位置を理解するために読むと良いですね。

6章の「コミットメント言語」だけでも読む価値有りです。

エラスティックリーダーシップ ―自己組織化チームの育て方

エラスティックリーダーシップ ―自己組織化チームの育て方

Scalatraの開発チームにjoinしました

「joinしました」って言いたいだけです。

細々とPRを送っていたらお誘い頂きましたので、Scalatraの開発チームにお誘い頂きました。最近ちゃんとしたOSS活動ができていなかったので、良い機会と思い、参加しました。

主にドキュメントや、サンプルコードがアップデートされていない箇所を直していますが、Scalatra 2.6のリリースに向けた開発も進んでいますので、積極的に参加しようと思っています。

Scalatra in Action

Scalatra in Action