その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

S3にホストされているMaven RepositoryをMaven (pom.xml) から参照する

S3にホストされているprivateなMaven Repositoryというものがあり、それをMaven (pom.xml) から参照する必要があったのでの方法についてのメモ。Gradleとかsbtとかからは割と使ったことはあったのだけれど、Mavenからは無かったので……

<project>
    ...

     <repositories>
        <repository>
            <id>your-repository-name</id>
            <url>s3://path/to/your/repository</url>
        </repository>
    </repositories>

    ...

    <build>

        ...

        <extensions>
            <extension>
                <groupId>com.github.seahen</groupId>
                <artifactId>maven-s3-wagon</artifactId>
                <version>1.3.3</version>
            </extension>
        </extensions>

        ...

    </build>
</project>

というふうに pom.xml 書いてあげると s3://path/to/your/repository でホストされているpackagesを dependencies から参照できるようになります。 maven-s3-wagon のバージョンについては適宜ドキュメントやリリースノートなどを見て調整しましょう。

AWSのアクセストークンについては READMEのAuthenticationのセクション の通りに解決されるので、そこを読むと良いでしょう。

簡単でしたね。良かった良かった。

protoc-gen-java-dynamodb書いた / protoc pluginはじめて書いた

protobufでスキーマを書いて protoc-gen-java-dynamodb に食わせると、そのスキーマJavaの生成コードに「生成されたDynamoDBのエンティティクラスのコード」を良い感じにねじ込んでくれるというprotocプラグインを書きました。

syntax = "proto3";

package com.example.dynamodb;

import "path/to/protoc-gen-java-dynamodb/protos/options.proto";

option java_package = "com.example.dynamodb";
option java_outer_classname = "ExampleEntityProto";

option (net.moznion.protoc.plugin.dynamodb.fileopt).java_dynamodb_table_name = "example-entity-table";

message ExampleEntity {
  string hash_key = 1 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_hash_key = true];
  int64 range_key = 2 [(net.moznion.protoc.plugin.dynamodb.fieldopt).java_dynamodb_range_key = true];
  bool bool_var = 3;
}

例えばこのようなproto3を書いてプラグインに食わせると、 ExampleEntityProto.ExampleEntity.DynamoDBEntity というinner classとして以下のようなコードが生成されます。

    // protoc-gen-java-dynamodb plugin generated (((
    @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable(tableName = "example-entity-table")
    public static class DynamoDBEntity {
      private String hashKey;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey()
      public String getHashKey() {
        return this.hashKey;
      }

      public void setHashKey(final String v) {
        this.hashKey = v;
      }

      private Long rangeKey;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey()
      public Long getRangeKey() {
        return this.rangeKey;
      }

      public void setRangeKey(final Long v) {
        this.rangeKey = v;
      }

      private Boolean boolVar;

      @com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute()
      public Boolean getBoolVar() {
        return this.boolVar;
      }

      public void setBoolVar(final Boolean v) {
        this.boolVar = v;
      }

      public DynamoDBEntity() {}

      public DynamoDBEntity(final String hashKey, final Long rangeKey, final Boolean boolVar) {
        this.hashKey = hashKey;
        this.rangeKey = rangeKey;
        this.boolVar = boolVar;
      }

      public ExampleEntity toExampleEntity() {
        return new ExampleEntity.Builder()
          .setHashKey(this.hashKey)
          .setRangeKey(this.rangeKey)
          .setBoolVar(this.boolVar)
          .build();
      }

    }

    public DynamoDBEntity toDynamoDBEntity() {
      return new DynamoDBEntity(this.getHashKey(), this.getRangeKey(), this.getBoolVar());
    }

    // ))) protoc-gen-java-dynamodb plugin generated

なのでprotobufでdeserializeしたオブジェクトに対して例えば obj.toDynamoDBEntity() としてやると生成された DynamoDBEntityインスタンスに変換ができ、それをDynamoDBMapper等に食わせることが可能です。逆に DynamoDBEntityインスタンスにも toSomethingActualClass() (SomethingActualClass の部分は実際のクラスによって変わります) という変換コードが生えているので、実際のクラスのオブジェクトに変換することも容易です。

くわしい使い方についてはREADMEをご参照ください。


モチベーションとしては、例えばDynamoDB Streamsを使っている時なんかに良くありがちですが「データをインサートするコンポーネント」と「ストリームに乗ってきたデータを処理するコンポーネント」とが別々の言語で記述されていると、それぞれのコンポーネントでテーブル定義を二重に定義しなければならない、みたいな状況になることがあってそれが嫌。それであればprotobufで定義を書いておいてそれを各言語向けにコード生成して使えば良いのではないか、ということで作られたというのがこのプラグインです。

同じような悩みがある方、ぜひご利用ください。




今回protoc pluginを書いたのははじめてで、正直なところまったくのnewbieなので世界観を掴むところからのスタートだったのですが、最初の一歩としては@yuguiさんの記事が大変参考になりました。

qiita.com

この記事で基本的なことを把握しつつ、あとは既存のプラグイン実装 (e.g. https://github.com/Fadelis/protoc-gen-java-optional) を見ながら公式ドキュメントを読んで実装していく……というふうに進めたところなんとか形になりました。

Javaでprotocプラグインを書くときは素手でやると結構泥臭い部分 (例えばJavaのbuiltin typeのハンドリングなど) があるので、そのあたりは既にあるフレームワークを部分的に使うと楽で良いです。

github.com

例えばSalesforceが出しているコレなどを活用すると面倒な部分を自分で手書きしなくて大変便利。

あと犯した勘違いとしては「protocのプラグイン (つまりexecutableなjar) だけを配信すれば使えるから、ライブラリとしてMaven Centralとかにアップロードしなくても良い」というものがあり、確かに自前でprotoのoptionを提供しない場合それは正なのですが、仮に独自のoptionを提供している場合はそのoptionの情報をJavaのコードとして提供する必要があります。従ってMaven Centralなりなんなりでライブラリコードも配信する必要があります。なのでこのプラグインのライブラリもMaven Centralにアップロードされています。

なおGoで似たようなことをしたい時はsrikrsna/protoc-gen-gotagという便利なやつがあって、これはprotoファイルの中にコメントで書いておくと良い感じで生成コード中のGoのカスタムタグに値を埋め込んでくれるというナイスなやつです。
当初はJavaでもこういうことをやろうと意気込んでいたのですが、Javaのprotocのライブラリはprotoファイル中のコメントを読む機能が無かったり *1、Plugin Insertion Point (つまり生成コードを捻じ込める箇所) が限られていたり *2、仮に protoc-gen-gotag と同じアプローチを採ってprotocによって生成されたコードを構文解析してそこに値をブチ込むにしてもJavaだと色々と大変そうだなあ……などなど色々と制限があったので今回書いたプラグインの形に落ち着いた次第です。

eclipse/leshan で最近直した問題について

かなりニッチな話題ですが、OMA LwM2Mのサーバ・クライアントのJava実装であるEclipse Leshanにパッチを送って取りこまれたのでそのご報告です。

github.com

Leshanのクライアントライブラリには予期せぬ例外が発生した際にクライアントアプリケーションがスタックし、プロセスは生き続けるものの一切の仕事をしなくなるという問題がありました。

Better handle unexpected error in DefaultRegistrationEngine. · Issue #933 · eclipse/leshan · GitHub


例えば以下のようなコードの箇所でRuntimeExceptionが発生すると、エラーログが記録されるだけで他のエラーハンドリングが成されません。

https://github.com/eclipse/leshan/blob/leshan-1.2.0/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java#L557

この RegistrationTask は個別のスレッドで動くのですが、意図しない実行時例外 (例えばUDP通信のレイヤで失敗するなど) が発生した場合はこのスレッド (RegistrationTask) はそれ以降何もしない、かつ孤児のような状況になり、孤児スレッドは回収されずプロセスは生き続けることとなります。
LwM2M的に register が成功しない限りプロトコルとして意味のある通信はできませんから、つまりこの状態のクライアントアプリケーションは一生register処理を行うことのできない、無意味なプロセスと化します。


その他にも複数箇所同様の問題がある部分があったため、それらを修正したというのが以下になります。

これらの変更により、不慮の例外が当該箇所で発生した場合は自動的にリソースを開放する (つまりシャットダウンを正しく行なう) ようになるため、何もしないゾンビのようなプロセス (注: ゾンビプロセスではない) は基本的に発生しなくなります。


基本的、と記述した理由は利用者が独自に定義して利用している LwM2mInstanceEnabler 及び LwM2mObjectEnabler に対して適切に StartableStoppableおよびDestroyableというそれぞれのInterfaceを実装してあげる必要があるためです。
不慮の例外が発生した際にしっかりと終了させたい場合は Destroyable を実装する必要がありますし、ホットな状態での再起動処理を正しく行いたい場合は Startable および Stoppable を適切に満足させる必要があります。
この詳細についてはLwM2mInstanceEnabler及びLwM2mObjectEnablerjavadocをご参照ください。


これらの修正はバージョン1.3.0以降から利用可能となっています。

github.com

現在開発中の次世代バージョンである2.0.0については順次対応予定 (2.0.0-M1よりも新しいバージョンで) となっているようです。

以上です。ご活用ください。

GitHub Packagesにホストされたprivate packageをsbtから使う

表題のとおりです.
GitHubのオフィシャルドキュメントを読むとmavenで使う方法gradleで使う方法は紹介されているのですがsbtで使う方法が調べてもシュッと出てこなかったのでメモとして記す次第.


基本的には以下のようにmavenやgradleと同様にbuild.sbtに対して設定してやるとよろしい.

libraryDependencies += "com.example.your" % "package" % "0.0.1"

resolvers += "GitHub your-package" at "https://maven.pkg.github.com/example/your-package"

credentials += Credentials("GitHub Package Registry", "maven.pkg.github.com", "", sys.env.get("GITHUB_REGISTRY_TOKEN").orNull)

libraryDependenciesは普通にインストールしたいprivate packageを指定し,併せてresolversにはそのpackageに至る宛先を追加します (この時,例における"GitHub your-package"は自分の好きなわかりやすい名前をつけて良い./example/your-packageについてはpackageの属する/org/repoを指定する).

credentialsについては例の通り,第4引数になんらかの形でGitHubトークン文字列を与えると良いです.この際のGitHubトークンの詳細についてはこちらを参照ください: https://docs.github.com/en/packages/publishing-and-managing-packages/about-github-packages#about-tokens
注意としては,Credentialsの第1引数および第2引数は例のとおりである必要があるということです.第2引数については「まあそうでしょう」という感じですが,第1引数を自由気ままに設定するとうまく動きません.これはこの文字列を内部的にrealmとして利用しているためです.

また,Credentialsについてはbuild.sbtに直接書くのではなく,別ファイルに書き出しておくという方法もあると思います (ref: https://www.scala-sbt.org/1.x/docs/Using-Sonatype.html#step+3%3A+Credentials).この辺は状況やお好みに応じてという感じでしょう.


以上です.GitHub Packages,便利ですね.

Javaの非同期アプリケーションのflame graph profileを取る

netty を使うような非同期 Java のアプリケーション (例えば Play2 Web アプリ) の flame graph profile を取るという話題です.色々な方法が考えられますが,jvm-profiling-tools/async-profiler を利用するのが最も手っ取り早そうな感じがしたので,その方法を示します.

github.com

使い方はいたって簡単で,Releases から任意のバージョンのアーカイブを取ってきて,

./profiler.sh -d 60 -f /path/to/flame_graph.svg $JAVA_APP_PID

として実行するだけで 60 秒間プロファイリングし,その結果の flame graph を取得することが可能です.

f:id:moznion:20200620234509p:plain

便利ですね.

jackson-databind で int の取りうる値を超えた場合の挙動

Javaの話題です.

jackson-databind を使って JSON のデシリアライズを行っていて,数値を int にマッピングしている場合,その値が int (32bit) の取りうる値を超えた時の挙動が「バージョンによって異なって」います.
以下に挙げる挙動は 2.9.3 から 2.9.4 へのアップグレードで変更されています.


jackson-databind-2.9.3 を利用している場合,値が int の範囲を超過するとその値を int にキャストしたもの *1 にデシリアライズされます.

コード:


一方,jackson-databind-2.9.4 で同様のシチュエーションになった時にどうなるかというと,このバージョンからは例外が上げられることになります.

コード:

新たに導入された _convertNumberToInt というメソッドの内部で値が int の取りうる範囲内かどうかを判断し,範囲内でない場合には reportOverflowInt で例外を送出するようになっています.


この変更がどこで入ったのかというと,どうやらこのコミットのようです: Fix #1729 · FasterXML/jackson-databind@6a1152c · GitHub

このコミットログに記されている #1729 がどのようなレポートかと言うと

github.com

「int が範囲外だった時,従来の実装では意図しない値になってしまうので明示的になるように例外を上げてほしい.jackson-core の ParserBase では int が範囲外の際に例外を上げている」という内容のようです.
Author の方も「挙動に一貫性を持たせるためにも,範囲外のときには例外を上げるようにしたほうがよい」と返答をしており,これについては賛成するところです (暗黙的な wraparound は脆弱性の元にもなり得るので).


さて一方で uint32 の範囲でおさまる値 (uint32 の範囲内だと値を負に rewind しても一意になる) を取り扱っているときに,従来の挙動を意識せず利用している場合,予期せぬクラッシュが起こります.
手っ取り早く直すためには int ではなく long として取り扱うというのが良いでしょうが,既存データとの兼ね合いなどの色々な事情によってそうもゆかないこともあるでしょう.そのような際にはカスタムシリアライザ・デシリアライザを書いて乗り切ることになると思います.

import com.fasterxml.jackson.databind.util.StdConverter;

public static class Uint32JacksonSerializer extends StdConverter<Integer, Long> {
    @Override
    public Long convert(Integer n) {
        if (n == null) {
            return 0L; // as you like
        }
        return n.longValue();
    }
}
import com.fasterxml.jackson.databind.util.StdConverter;

public static class Uint32JacksonDeserializer extends StdConverter<Long, Integer> {
    private static final long MAX_UINT32_VALUE = 4294967295L;

    @Override
    public Integer convert(Long n) {
        if (n == null) {
            return 0; // as you like
        }
        if (n > MAX_UINT32_VALUE) {
            throw new IllegalArgumentException("out of the boundary of uint32 value: " + n);
        }
        return n.intValue();
    }
}

このようにカスタム (デ) シリアライザを書いて,

public class JsonClass {
    @JsonSerialize(converter = Uint32JacksonSerializer.class)
    @JsonDeserialize(converter = Uint32JacksonDeserializer.class)
    private Integer foo;
}

などとしてあげると,入れる時・出す時によしなに値を変換して取り扱ってくれるようになります.



しかしこれがパッチバージョンとして入ってくるレベルの変更かというと……大変ですね.まあリリースノートにはしっかり書いてあるのですが……jackson-databind/VERSION-2.x at ae9c91d254954a963cc525e941564c5348181eac · FasterXML/jackson-databind · GitHub

教訓としては「bit長にゆとりを持ってデータ設計をしましょう」「リリースノートをちゃんと読もう」ということです,現場からは以上です.

*1:例えば `2147483648` が来た場合に rewind されて `-2147483648` になる

CompletionStage<T>な変数を同期的に処理したいんですけど〜ってとき

Javaの話です.

CompletionStage<T>を返却するようなメソッドがあって,それを同期的に処理したい (単体テストを書くというシーンが最も多いでしょう) ということが生きているとあります.しかしCompletionStageget()のようなメソッドが生えていないので同期的に処理することができません.thenAccept()を使おうにも,例えばJUnitのテストケースではすっぽ抜けてしまってうまく扱えません (逆にテストケース以外ではうまく働くでしょう).

そんなときにどうするかというと,手っ取り早い方法はCompletableFuture<T>にキャストしてしまうことでしょう.すると,オブジェクトにget()が生えてくるので同期的に値を取り扱うことが可能となります.

例:

final CompletionStage<String> completionStage = getSomething();
final CompletableFuture<String> future = (CompletableFuture<String>) completionStage;
final String got = future.get(); // ここで同期的に取り扱える

最初からCompletableFutureで値を返却してほしいですね.以上です.