u-ryo's blog

various information for coding...

Java8 DateTime API

| Comments

Java8から入ったDateTime API(java.time package)では、 a.plusMinutes(3).isBefore(now) なんていう感じで日付時刻計算や比較が出来ます、 という話です。現在時刻はZonedDateTime.now(ZoneId.of("Asia/Tokyo"))。 あ、ZonedDateTime.now()でもいいんじゃーんdefaultのtimezoneでいぃなら。

GroovyFX

| Comments

SwingからJavaのGUIの座を引き継いだとはいえ、 Java11ではJava本体から削除され、 OpenJFXとなるJavaFX、 でもGUI作るならJavaFXだろうなぁ、 簡単に描けるようにGroovyでないかなー、 と思ったら、ありましたGroovyFX。 早速試してみると、 本家に書いてある「Hello, World」すら以下のように失敗しました。

1
2
3
4
5
6
7
8
9
10
11
Caught: java.lang.ExceptionInInitializerError
java.lang.ExceptionInInitializerError
        at java_lang_Class$isAssignableFrom$1.call(Unknown Source)
        at com.sun.proxy.$Proxy8.onClassInfo(Unknown Source)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
        at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:550)
        at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:512)
        at javafx.scene.control.Control.<clinit>(Control.java:87)
        ... 2 more

色々試すと、以下のような最低限のscriptでもaaaを出力もせず 全く同じerror messageを出すので、 もうGrapeでgroovyfxを読み込んでいる時点でダメなんでしょう。

1
2
3
@Grab('org.groovyfx:groovyfx:8.0.0')
import java.lang.*
println('aaa')

scriptに何を書いても無視される(そこまで到達しない)わけなので、 事前にnew javafx.embed.swing.JFXPanel()して JavaFX environmentを初期化する、ということも当然効きません。 Grapeに拘っていたので、 古いversion(@Grab('org.codehaus.groovyfx:groovyfx:0.4.0'))に してnew javafx.embed.swing.JFXPanel()しておけば、 start{...}が動くことがわかりversion落としてやってましたが、 今改めて試してみると、 Grapeじゃなくてgroovy -cp groovyfx-8.0.0.jar test.groovyと Class Pathを指定すれば、 new javafx.embed.swing.JFXPanel()もなしに 「Hello World」もその通り動くことが確認できました。 そっかー、Grape諦めればよかったのかー。 -cpを指定しても足りないところはGrapeも効くので、 そぃでよかったでもんそ。

あと、MySQLからdata読む必要があったので、

1
2
3
4
5
6
7
8
@GrabConfig(systemClassLoader=true)
@Grab('mysql:mysql-connector-java')
import groovy.sql.*

def sql = Sql.newInstance('jdbc:mysql://localhost:3306/database?useSSL=false', 'user', 'password', 'com.mysql.cj.jdbc.Driver')
sql.eachRow('SELECT * FROM table WHERE filename LIKE ?', ['%' + filename]) { r ->
  ...
}

とすればよいと。

今回の目的は、画像を読み込んで、それにDBから読み込んだ値で四角形を描き、 そぃを画像fileとして出力する、ちゅうもんじゃった。 scene.snapshot()してからSwingFXUtils.fromFXImage(scene, null)で 描画画像をbitmap化出来ます。 それをImageIO.write()で一発でfileに書けるがじゃっどん、 そぃだと何か出来た画像が赤みがかっちゃっちょるんですよね。 どげんしたもんか、試行錯誤の挙句、ImageMagickのfrontend、 im4javaを通すとうまく行きました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.im4java.core.*

color = new Color(0.753, 0.302, 0.314, 0.4)
op = new IMOperation().addImage().quality(100.0).strip().addImage()
convert = new ConvertCmd()

start {
  st = stage(title: 'any words OK', visible: true) {
    s = scene(fill: BLACK, width: w, height: h) {
      imageView(new Image(Paths.get(imageFileName).toUri().toString()))
      rectangles.values().forEach { v ->
        rectangle(x:v[0], y:v[1], width:v[2], height:v[3], fill:color)
      }
    }.snapshot(new WritableImage(w, h))
    convert.run(op, SwingFXUtils.fromFXImage(s, null), imageFileName.replace('.jpg', '_.jpg'))
    // ImageIO.write(SwingFXUtils.fromFXImage(s, null), 'jpg', new File(imageFileName.replace('.jpg', '_.jpg')))
  }
  st.close()
}

最後、stage.close()すればwindowは開かずに済みます。

Getting the First in Spring DATA JPA

| Comments

Spring DATA JPAで、「最新のもの一つ」を取得したかったんです。 Spring DATA JPAは、findFirstBy...とかってmethodに命名すれば 自動的にSQL作ってくれるらしいんですが(【Spring Data JPA】自動実装されるメソッドの命名ルール)、 目的のものではlogin userを自動的にparameterizeしたかったので、 それが出来ませんでした。←User objectは別途取得しておいて、 それをparameterに入れれば良かったかも、ですけど。 ともあれ、findFirstBy...で出来ないなら、 Pageableを付けるしかなさそうだ、ということで、 SQL文にはMySQLでいうところのlimit=1などはつけずに引数の最後にPageableを添え、 new PageRequest(0, 1, DESC, "to")として範囲を指定しました(org.springframework.data.domain.Sort.Direction.DESC)。

参考URLs

  1. yamkazu/springdata-jpa-example
  2. Spring Data JPA Tutorial: Pagination
  3. setMaxResults for Spring-Data-JPA annotation?

Arukas Cloud

| Comments

Sakuraがやっているからそのreverse spellingだというArukas、 docker hostingをやっていて、 credit card登録は強制されますが1 containerなら無料というので、 専用SSH machine作ってJHipsterの開発/buildに使おうと企みました。

無料枠ではDocker Hubにあるものしか使えないとはいえ、 SSH serverの入ったdocker imageなんて色々あります。 でも使ってみると、基本的にはrootでlogin、 当然root loginをpermitしていて、 securityを維持するにはrootのpasswordを変える、 userを作って.ssh/authorized_keysを作る、 とかなわけですが、けどroot loginのpermissionを切って sshdをrestartすると、docker終わっちゃうんですよね当たり前ですが。 何かもっとこう、最初からUser作ってrootは禁止して、 とかっていうの無いかなー、 と物色していると、どれだったか忘れましたがgithub.comから 自分が登録したkeyをADDしてるものがあって、 あーなるほどー、と思って。 ADD https://github.com/$GITHUB_USER.keys /home/$GITHUB_USER/.ssh/authorized_keysということですね。

で、結局どうしても自分の欲しいものがなかったので、 Dockerfile自作することにしました。 元になるimageはDocker Hub上になければいけないので、 Docker Hubを使ってGitHubにあるDockerfileからimageを自動生成するを参考に、 Github → Docker Hub → Arukasという流れになるように、 自分のGithub repository自分のDocker Hub repository自分のArukas App と繋げて、 回るようにします。

Arukasでは変数定義が出来るというのでDockerfileでGithub user名をENV定義して、 でもそうするとDocker HubではbuildにコケるのでENVには何かdefault値がないとダメで、 でもdefault値を付けちゃうとそのdefault値がArukas Appまで残っちゃうんですよね。 Docker コンテナの動作に必要な設定を起動時に渡すなどを見て、 え、shell script内じゃないとダメ? じゃぁっていうんでentrypoint.sh作って試してみたんですけど、 Arukasで起動に失敗しました。Docker Hubではbuild通ったのに。 Arukasでは起動時のmessageとかは出ないので、何が悪いのか分かりません。 困り果ててArukasのchatで相談投げると、翌営業日にはすぐreplyをくれて、 動作確認のためということでpull requestまで作ってくれました。 まずは早速そのまま取り込んで起動してみたんですが、 やはり起動に失敗。 けれども、別のaccountの方でENVを編集することで再起動させてみたところ うまく行ったので、何だったんでしょう? 結局今はどちらでも上手く立ち上がるようになりました。 ありがとうございました。

他、Arukasでハマったのは、開放するPort、 後で色々service立ち上げようと思って3000番とか8080番とか色々書いておいたら、 Endpointが有効にならないんですね。 これもArukasのchatで聞いて教えてもらったんですが、 確かによく読むと、「Docker image立ち上がった時点で 書いたPortが全て開いてないとEndpointが有効にならない」 と書いてありますね。 「一番上に書いたPortだけEndpointに繋がる」というのは 「アプリ編集」画面のEndpointのinfoに書いてあるからわかったんですが。

baseとなるdistributionは、 軽いというAlpine Linuxに。 アプリの管理もapk add ...コマンドで出来ますし、 packageもscreenやらopenjdk8やらgradleやら 結構色々あってびっくりです。 何かubuntuじゃなくてalpineで十分な気がしてきました。

目的としていたjhipster applicationのbuildは失敗しました。 specがしょぼい(128MB memory)のか通信量に制限があるのか、 jhipsterコマンドの途中で止まり、 applicationのscaffoldingが出来ません。 まー、無料枠なのでmachine specについては文句言えませんから、 しょーがないのかなーと。

ただ、Endpointがhttpsで手に入るので、 Nginxによるリバースプロキシの設定方法を参考に、 nginxによるreverse proxyとして使おうかなー、 と思っています。 custom domainも使えるとのことですが、 CDNと同じくDNSを自分でcontrol出来ねばならないので、 dynamic DNSでは使えず、custom domainは諦めました。

  • docker hubの成果物(といってもpull requestくれた山田さんのおかげモノですが。山田さんありがとうございます!)

Githubにkeyを登録してあれば、Arukas AppでImageにこれ↑を指定し、 ENVGITHUB_USERをGithubのuser名、 PROXY_PASSをreverse proxyしたいURL、 にして立ち上げると、 当該hostにssh出来、 またEndpointで指定したURLでreverse proxy出来ます。

1
2
ln -sf /dev/stdout /var/log/nginx/access.log
ln -sf /dev/stderr /var/log/nginx/error.log

こんな技があるんですねーなるほどー。

Stubbing in Spock

| Comments

Spockでstubbingして methodのcall回数をassertする必要がありまして。 someClass = Spy(SomeClass)でspyにすると、 3 * someClass.targetMethod(_, _)といったようにassert出来るのですが、 そのtargetMethodを呼ぶ大元のmethodのcallは、 then:ではなくwhen:になければならなかった、という話です。

即ち、

1
2
3
4
5
when:
  ...
then:
  someClass.method() == 'answer'
  3 * someClass.targetMethod(_, _)

ではダメで、

1
2
3
4
5
when:
  ans = someClass.method()
then:
  ans == 'answer'
  3 * someClass.targetMethod(_, _)

でないとなりませんでした、と。

あと、 3 * someClass.targetMethod(_, _)の部分には、 変数とか入れられません。即ち、 (3 + n) * someClass.targetMethod(_, _)とかはダメでした。