switch式の値付きbreakはyieldになる?

前のエントリではswitch式で値を返すのはbreak-withになると書いてましたが、yieldにしようぜーという議論が勃発しています。
switch式の値付きbreakはbreak-withになることがほぼ確定(追記あり) - きしだのHatena

というか、JEPはすでにyieldになっています。
JEP 354: Switch Expressions (Preview)

https://mail.openjdk.java.net/pipermail/amber-spec-experts/2019-May/001301.html

これは10日ほど前のBrian Goetz氏からの「自転車置き場の議論やろうぜー」という投稿から議論がはじまっていて、ハイフン付きキーワードだけじゃなくコンテキスト付きキーワードも考慮しようという話です。
Call for bikeshed -- break replacement in expression switch

反論としては yield (1);がyieldメソッドに引数1を渡すのか、(1)をswitch式の結果として返すのかわかりにくいんでは、特にIDEの実装が大変なんでは、というのが出てますね。
Yield as contextual keyword (was: Call for bikeshed -- break replacement in expression switch)

どうなるか興味津々

switch式の値付きbreakはbreak-withになることがほぼ確定(追記あり)

Java12でプレビューとしてswitch式が入って、Java13で正式化できるよう作業が進んでいます。 そんな中、switch式を正式化するJEPのドラフトが出ていました。 JEP draft: Switch Expressions 追記 あっというまにドラフトではなくなっています JEP 354: Switch Expressions

プレビューとの違いはbreakがハイフン付キーワードのbreak-withになるという記述があります。

int result = switch (s) {
    case "Foo": 
        break-with 1;
    case "Bar":
        break-with 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break-with 0;
};

かなり違和感が・・・
そしてこれは、package-privateとかいろんなキーワードが現れる準備でもありそうです。
引き算と区別ができないんでは?という点について、ハイフン付キーワードは必ずキーワードとの組み合わせになるはずなので、もともと引き算とはみなせなかったので大丈夫です。

5/22追記: break-withじゃなくてyieldになるかも。JEPが変更されています。しかし議論が大盛り上がり中でどこに落ち着くかわからない状況。 switch式の値付きbreakはyieldになる? - きしだのHatena

QuarkusのHibernate ORM with Panacheでid不要ならPanacheEntityBaseを使う

Quarkusを試していて、Hibernate ORM with Panacheを使ってみると、ちょっとハマった。

こんな感じのEntityクラスを作った。

@Entity
@Table(name = "users")
@Data
public class User extends PanacheEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

   ...

そうするとUser.listAll()を呼び出したときにこんなエラー。idフィールドがないと言ってる。

Caused by: org.postgresql.util.PSQLException: ERROR: column user0_.id does not exist

いろいろ試したのだけど、なんのことはない、PanacheEntityidが定義されているので、代わりにPanacheEntityBaseを使えばよかった。

@Entity
@Table(name = "users")
@Data
public class User extends PanacheEntityBase {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

   ...

jpackageでJavaアプリのインストーラーを作る

Javaアプリケーションのインストールパッケージを作れるjpackageが、どうもJDK13に入りそうな勢いでEarly Accessが出てたので試してみます。
jpackageは、JavaFXにあったjpackagerをベースにしたパッケージングツールです。
JEP 343: Packaging Tool

EAはこちらからダウンロードできます。開発中のJDK13をベースにしてると書いてありますね。
jpackage

Windowsで必要なもの

Windowsインストーラーを作るには、iscc.exeが必要で、これはInno Setupに入ってます。
jrsoftware.org // Jordan Russell's Software

あと、MSI形式のインストーラーを作るにはlight.exeとcandle.exeが必要で、これはWix toolsに入ってます。
WiX Toolset
zipでwix311-binaries.zipをダウンロード・解凍してパスを通すのがいいと思います。

インストーラを作る

ではインストーラを作ってみます。
とりあえずウィンドウを作るアプリを作ってみます。

import javax.swing.*;

public class MyFrame {
    public static void main(String[] args) {
        var f = new JFrame("My Package");
        var t = new JTextArea();
        f.add(t);
        var b = new JButton("Hello");
        f.add("North", b);
        b.addActionListener(al -> t.append("Hello\n"));
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(500, 400);
        f.setVisible(true);
    }
}

ボタンを押すとHelloと表示されるだけのアプリ
実行画面

jpackageではclassファイル単体ではパッケージを作れないようなので、一度jarを作ります。

> javac MyFrame.java

> mkdir target

> jar -cf target/mypack.jar MyFrame.class

そしたら、jpackageでインストーラを作成

>jpackage create-installer -ooutput pack -input target --name MyApp --main-class MyFrame --main-jar mypack.jar
Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed
 by either disabling realtime monitoring, or adding an exclusion for the directory "C:\Users\naoki\AppData\Local\Temp\".
Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed
 by either disabling realtime monitoring, or adding an exclusion for the directory "C:\Users\naoki\AppData\Local\Temp\".
Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed
 by either disabling realtime monitoring, or adding an exclusion for the directory "C:\Users\naoki\AppData\Local\Temp\".

こんな感じでexeとmsiインストーラができています。--typeで形式を指定することもできます。

>dir pack
 C:\Users\naoki\Documents\mypack\pack のディレクトリ

2019/03/06  05:39    <DIR>          .
2019/03/06  05:39    <DIR>          ..
2019/03/06  05:38        33,266,979 MyApp-1.0.exe
2019/03/06  05:39        53,588,671 MyApp-1.0.msi

exeが33MBでmsiが53MBですね。
exeを実行すると、インストーラ画面がでます。
インストーラ画面

インストール実行すると、あのインストール画面が。
インストール実行

スタートメニューにも追加されています。 スタートメニュー

もちろん、このアイコンをクリックすれば起動します。
実行画面

ところで、msiをダブルクリックするといきなりインストール始まるので注意です。

ファイルはWindowsの場合C:\Program Filesにインストールされます。
jshell.exeなど、JDKのファイルも含まれてることがわかります。 JDKもインストールされる

JDKまるごと含まれてると考えたら、50MBでインストーラは妥当な感じですね。

「アプリと機能」からアンインストールもできます。
アプリと機能

JDKだけのインストーラも作れるみたい。なかなか便利ですね。

MicronautのメトリクスとPrometheus

MicronautでPrometheusでメトリクス取りたい話

Micronautはmicrometer経由でprometheusに対応してるので、create-appするとき--featuresにmicrometer-prometheusつけるとそれっぽいdependencyや設定を生成してくれます。

$ mn create-app foo --features micrometer-prometheus

Gradleはこんな感じ。

    compile "io.micronaut.configuration:micronaut-micrometer-core"
    compile "io.micronaut.configuration:micronaut-micrometer-registry-prometheus"

設定はこんな感じ。

micronaut:
    metrics:
        enabled: true
        export:
            prometheus:
                enabled: true
                step: PT1M
                descriptions: true

それで http://localhost:8080/metrics/ にアクセスするとメトリクス名の一覧がとれます。

{
  "names": [
    "executor",
    "executor.active",
    "executor.completed",
    "executor.pool.size",
    "executor.queued",
    "hikaricp.connections",
...
    "process.uptime",
    "system.cpu.count",
    "system.cpu.usage"
  ]
}

さらにその名前を指定すると具体的な値がとれる感じ
http://localhost:8080/metrics/hikaricp.connections

{
  "name": "hikaricp.connections",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 0
    }
  ],
  "availableTags": [
    {
      "tag": "pool",
      "values": [
        "HikariPool-1"
      ]
    }
  ]
}

ということでDockerでPrometheusをたてます。
こんな感じの設定ファイルを適当にローカルに置いておきます。targetsの位置はホストのIP。

global:
    scrape_interval: 5s
    
scrape_configs:
    - job_name: 'mystat'
      metrics_path: '/prometheus'
      static_configs:
          - targets:
              - '172.17.0.1:8080'

あとはDockerにその位置をおしえてあげればOK

$ docker run -d --name mystat-prom -p 9090:9090 \
   -v /c/Users/naoki/NetBeansProjects/my-stat-mn:/prom-data \
   prom/prometheus --config.file=/prom-data/prometheus.yml

とするとPrometheusは起動するんですが、MicronautからPrometheus用の情報がでてきません。

なんかこんな感じでEndpointを作ってあげる必要があるっぽい。

import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micronaut.management.endpoint.annotation.Endpoint;
import io.micronaut.management.endpoint.annotation.Read;
import javax.inject.Inject;

@Endpoint(id = "prometheus", value="/prometheus",
   defaultEnabled=true, defaultSensitive=false)
public class PrometheusEndPoint {
    @Inject
    private PrometheusMeterRegistry prometheus;

    @Read
    public String scrape() {
        return prometheus.scrape();
    }
}

Micronaut: How to get metrics in the Prometheus format? - Stack Overflow

そしたらPrometheusでデータがとれるようになりました。

f:id:nowokay:20190217213656p:plain
Prometheus

PostgreSQLのリアクティブアクセスをネイティブ化できなかった

とりあえずメモ的に。
R2DBCとmicronaut+reactive-pg-clientの2通りでnative-imageを試したけど、nettyまわりでだめだったという話

R2DBC

リアクティブデータアクセスとしては本命?
R2DBC
R2DBCはセントラルリポジトリにないので、springのリポジトリを使います。

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>    
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>        
    </repositories>

R2DBCのライブラリはこれ。

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <version>1.0.0.M6</version>
        </dependency>

コードはこんな感じで。

import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PostgresqlConnectionConfiguration conf = 
                PostgresqlConnectionConfiguration.builder()
                                                 .host("localhost")
                                                 .port(5432)
                                                 .database("mystat")
                                                 .username("mystat")
                                                 .password("pass").build();
        PostgresqlConnectionFactory connFact = 
                new PostgresqlConnectionFactory(conf);
        ExecutorService es = Executors.newSingleThreadExecutor();
        Scheduler sc = Schedulers.fromExecutorService(es);

        connFact.create()
                .map(conn -> conn.createStatement("select * from USERS"))
                .flatMapMany(stmt -> stmt.execute())
                .flatMap(result -> result.map((row, meta) -> 
                        String.format("handle:%s name:%s",
                    row.get("user_handle"), row.get("user_name"))))
                .subscribeOn(sc)
                .doOnTerminate(() -> es.shutdown())
                .subscribe(System.out::println);
    }
}

Schedulerにはelastic()が楽なんだろうけど、コマンドラインだと処理が終わる前にコマンドが終了して何も出力されないので、ExecutorServiceを用意してfromExecutorService(es)を使います。で、doOnTerminateshutdown()
もっといい方法があったら教えてください。
native-imageするのでJava8文法しか使えないからvarはナシ。newとかbuilderとかはvarで受けたいけど。

そしてnetty関係でダメでした。

R2DBC-client

R2DBCはちょっとラップして便利にしてくれるclientがあるので使ってみる。

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-client</artifactId>
            <version>1.0.0.M6</version>
        </dependency>

こんな感じ。ちょっと短くなった。

import io.r2dbc.client.R2dbc;
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PostgresqlConnectionConfiguration conf = 
                PostgresqlConnectionConfiguration.builder()
                                                 .host("localhost")
                                                 .port(5432)
                                                 .database("mystat")
                                                 .username("mystat")
                                                 .password("pass").build();
        PostgresqlConnectionFactory connFact = 
                new PostgresqlConnectionFactory(conf);
        ExecutorService es = Executors.newSingleThreadExecutor();
        Scheduler sc = Schedulers.fromExecutorService(es);
        
        R2dbc r2dbc = new R2dbc(connFact);
        r2dbc.inTransaction(h -> 
                h.select("select * from USERS")
                        .mapRow(row -> row.get("user_name")))
                .subscribeOn(sc)
                .doOnTerminate(() -> es.shutdown())
                .subscribe(o -> System.out.println(o));
    }
}

まあ、もとのR2DBCでこけてるので、もちろんnative-imageはnettyまわりでこける。

Reactive Postgres Client

もうひとつ、PostgreSQL用にはReactive Postgres Clientというのがある。
Reactive Postgres Client | reactive-pg-client

Micronaut経由で使ってみたらnative-image対応してくれてるんでは、と試してみる。
とはいえ、Webでやるのは面倒なので、CLIとして使ってみます。
Micronaut: 11. Standalone Command Line Applications

Micoronautのプロジェクト作成コマンドmnで大枠を作ります。

$ mn create-cli-app my-cli --features postgres-reactive

build.gradleにソースレベルなどの設定を追加

sourceCompatibility="1.8"
targetCompatibility="1.8"

application.ymlにPostgreSQL関係の設定がすでに書いてあるので、適切に変更。ここで、clientの下にあるべきものたちのインデントがちゃんとしてない気がするので修正してます。

postgres:
    reactive:
        client:
            port: 5432
            host: localhost
            database: mystat
            user: mystat
            password: pass
            # maxSize: 5

修正のPRでも送るか~と思ったけど、ぺろぺろっとできる修正じゃなさそうなので保留
あとは、MyCliCommand.javaができてるはずなので、処理を書きます。

public void run() {
    // business logic here
    if (verbose) {
        System.out.println("Hi!");
    }
    data();
}

@Inject
PgPool client;

public void data() {
    client.rxQuery("select * from users")
            .map(rowSet -> {
                List<String> result = new ArrayList<>();
                PgIterator ite = rowSet.iterator();
                while(ite.hasNext()) {
                    Row row = ite.next();
                    result.add(row.getString("user_name"));
                }
                return result;
            })
            .blockingGet()
            .stream()
            .forEach(System.out::println);
}

println("Hi!")は元からある処理。
PgPoolをInjectするだけで使えます。io.reactiverse.pgclient.PgPoolではなくio.reactiverse.reactivex.pgclient.PgPoolなので注意。
しかし、R2DBCはReactorだけどReactive-pg-clientはRxJavaなので戸惑う。

そしてnative-imageしてみるけど、やはりnetty関係でだめだった。

そのうちリベンジする。

PostgreSQLへのJDBCアクセスをネイティブ化する

PostgreSQLへのJDBCアクセスがあるコードをGraalVMでネイティブイメージ化するとき、org.postgresql.core.v3.ConnectionFactoryImplの対応が必要だったのでメモ

たとえばこんな感じでPostgreSQLにアクセスします。

public class Main {
    public static void main(String[] args) throws SQLException {
        try (Connection conn = DriverManager.getConnection(
                "jdbc:postgresql://localhost:5432/mystat", "mystat", "pass");
             Statement stmt = conn.createStatement();
             ResultSet result = stmt.executeQuery("select * from users")) {
            while (result.next()) {
                System.out.printf("name:%s handle:%s %n",
                        result.getString("user_name"),
                        result.getString("user_handle"));
            }
        }
    }
}

JDBCドライバは42.2.5を使ってます。

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>

これをネイティブコンパイルすると、なんかエラーが出ました。

$ native-image -jar AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies.jar
Build on Server(pid: 153, port: 56330)*
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:153]    classlist:   2,296.43 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:153]        (cap):   3,746.53 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:153]        setup:   5,098.37 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:153]     analysis:   5,581.32 ms
error: unsupported features in 4 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: com.sun.jna.Platform. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time.
Trace:
        at parsing org.postgresql.sspi.SSPIClient.isSSPISupported(SSPIClient.java:90)
Call path from entry point to org.postgresql.sspi.SSPIClient.isSSPISupported():
        at org.postgresql.sspi.SSPIClient.isSSPISupported(SSPIClient.java:90)

ぐぐってみると、なんかスタブが必要そう。

native-image fails to compile PostgreSQL JDBC client. · Issue #727 · oracle/graal

このようなクラスで対応できるみたいです。

https://github.com/katox/graal-pg-client/blob/master/src/main/java/stub/InternalConnectionFactoryImpl.java

コンパイルするにはSubstrateVMのライブラリが必要です。

<dependency>
    <groupId>com.oracle.substratevm</groupId>
    <artifactId>svm</artifactId>
    <version>1.0.0-rc11</version>
    <scope>provided</scope>
</dependency>

いけました!

$ native-image -jar AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies.jar
Build on Server(pid: 939, port: 58056)*
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]    classlist:   2,031.76 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]        (cap):   2,046.18 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]        setup:   3,208.73 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]   (typeflow):   5,918.46 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]    (objects):   4,188.45 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]   (features):     367.38 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]     analysis:  10,700.59 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]     universe:     441.69 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]      (parse):     690.96 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]     (inline):   1,360.90 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]    (compile):   5,880.47 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]      compile:   8,622.91 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]        image:     932.34 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]        write:     365.44 ms
[AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies:939]      [total]:  26,385.43 ms

ただ、なんかもう一度native-imageするとエラーがでます。サーバーを使わないようにしたほうがよさそう。

ネイティブコンパイルすると、やたら速いのですごく違和感がありますね。

$ time java -jar AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies.jar
name:John Smith handle:john

real    0m0.699s
user    0m0.609s
sys     0m0.500s
$ time ./AccessPostgres-1.0-SNAPSHOT-jar-with-dependencies
name:John Smith handle:john

real    0m0.033s
user    0m0.000s
sys     0m0.016s