saba1024のブログ

どうでも良い思いついた事とかプログラミング関係のメモとか書いていきます。

GroovyのGrape/Grabでdownload failedが発生する際の対処方法

Groovyでは、プログラムの実行時にMavenリポジトリから自動的に依存ライブラリのダウンロード、クラスパスの設定まで行ってくれるGrape/Grabという機能が有ります。 使い方自体は簡単で、コードの先頭に、

@Grab('org.apache.solr:solr-core:6.6.0')
@Grab('org.slf4j:slf4j-simple:1.7.12')
@Grab('commons-codec:commons-codec:1.10')

もしくは、

@Grapes([
    @Grab(group='commons-codec', module='commons-codec', version='1.10'),
    @Grab(group='org.slf4j', module='slf4j-simple', version='1.7.12', scope='test'),
    @Grab(group='org.apache.solr', module='solr-core', version='6.6.0')
])

のような指定をするだけです。
基本的にこれだけで問題ないのですが、時々以下のようなエラーに遭遇することが有ります。

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: Error grabbing Grapes -- [download failed: commons-codec#commons-codec;1.10!commons-codec.jar, download failed: org.slf4j#slf4j-api;1.7.7!slf4j-api.jar]

java.lang.RuntimeException: Error grabbing Grapes -- [download failed: commons-codec#commons-codec;1.10!commons-codec.jar, download failed: org.slf4j#slf4j-api;1.7.7!slf4j-api.jar]
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
...Stacktraceが続く

ダウンロードが出来ない、ということなのですが、一体なぜなのか。。。
対処療法的ではありますが、手動でjarをダウンロードすることでこの問題を回避することが出来ます。
手動ダウンロードかよ。。。という話ではありますが、classpathを通さなくても良いのでGroovyConsoleなどから簡単にこれらの外部依存ライブラリを扱うことが出来る点は変わりありません。

標準だと、Grape/Grabでダウンロードされるjarファイル群は全て$HOME/.groovy/grapes配下に保存されます。
更にそのディレクトリから、グループ名/モジュール名 というディレクトリ構成になっています。
今回問題になっている、commons-codec1.10なので実際にそのディレクトリを見てみます。

[koji:~]$ cd $HOME/.groovy/grapes/commons-codec/commons-codec
[koji:commons-codec]$ ls -al
合計 40
drwxr-xr-x 3 koji koji  4096 Aug  2 14:47 .
drwxr-xr-x 3 koji koji  4096 Aug  2 14:34 ..
-rw-r--r-- 1 koji koji 10943 Jan  2  2017 ivy-1.10.xml
-rw-r--r-- 1 koji koji 11609 Jan  2  2017 ivy-1.10.xml.original
-rw-r--r-- 1 koji koji   873 Aug  2 14:39 ivydata-1.10.properties
[koji:commons-codec]$ 

何やらファイルが存在しています。
本来は、このディレクトリにjarsというディレクトリが有り、その中に実際のjarファイルが格納されています。(本来はGrape/Grabによってダウンロードされる)
それが存在していないので、手動でjarsを作って、Mavenリポジトリからjarを手動でダウンロードしてきます。

[koji:commons-codec]$ mkdir jars
[koji:commons-codec]$ wget http://central.maven.org/maven2/commons-codec/commons-codec/1.10/commons-codec-1.10.jar -P jars

これで完了です。 エラーになっていたGrape/Grabを使っているコードを再度実行すれば問題なく実行されるようになっています。

Groovyで簡単なS式をパースするサンプル

無駄に複雑なコードになってしまっていますが、一応四則演算は実行できるS式のパーサが出来ました。
間違いなくもっとスマートな方法があるはずですが(普通にググれば10行程度でRubyで実装している方もいらっやいます)、今まで基本的にWebプログラミングしかして来なかったのでこういったパーサ系なんかも全部ライブラリ任せにしてしまってたので、まずは自分のための勉強ということで。。。
これを機会にこういったまた普段とは違ったコードも書いていきたいなと思います。

trait Parser {

    Closure findLastList = {l, next ->
        if (next <= 0 || l == [] || !(l instanceof List)) {
            l
        } else {
            call(l.last(), next-1)
        }
    }

    Closure normalizeSpaces = {String str ->
        str = str.replaceAll(/\(/, ' ( ')
        str = str.replaceAll(/\)/, ' ) ')
        // 先頭か末尾にスペースがある場合は1個に正規化されるけど必要ないのでtim()
        str.replaceAll(/\s{1,}/, ' ' ).trim()
    }
    
    Closure parseToList = {List<String> tokens ->
        List list = []
        Integer nest = 0
        tokens.each {
            if (it == '(') {
                if (nest > 1) {
                    findLastList(list, nest-1) << []
                } else {
                    list << []
                }
            nest++
            } else if (it == ')') {
                nest--
            } else {
                if (nest > 1) {
                    findLastList(list, nest-1) << it
                } else {
                    list << it
                }
            }
        }
        list.tail()
    }
    
    List<String> parse(String sourceCode) {
        String normalized = normalizeSpaces(sourceCode)
        List<String>tokens = normalized.tokenize(' ')
        parseToList(tokens)
    }
}

class MyLanguage implements Parser {

    Map operations = [
        '+': {a, b -> (a as Integer) + (b as Integer)},
        '-': {a, b -> (a as Integer) - (b as Integer)},
        '*': {a, b -> (a as Integer) * (b as Integer)},
        '/': {a, b -> (a as Integer) / (b as Integer)},
    ]
    
    Closure execute = {List code -> 
    
        String operation = code.head()
        List<String> operands = code.tail()

        def o = []
        for (obj in operands) {
            o << ( (obj instanceof List) ? call(obj) : obj )
        }
        o.inject(operations[operation])
    }

    def eval(String sourceCode) {
        List<String> parsedAsList = parse(sourceCode)
        execute(parsedAsList)
    }
}

// 以下のように、ネストしたS式でもちゃんと四則演算ができている。
def engine = new MyLanguage()
assert engine.eval('( + 1 2 3 ( - 1 2 ) )') == 5
assert engine.eval('(* 3 5)') == 15
assert engine.eval('(/ 20 2 5)') == 2
assert engine.eval('(* 2 (/ 10 5))') == 4
assert engine.eval('(+ (* 2 4) 5)') == 13
assert engine.eval('(- (+ (* 2 4) 5 (/ 6 2)) 10)') == 6

Ratpackを使ってJsonBuilderとJsonSlurperを試す

概要

GroovyでJSONを生成する(JsonBuilder)という記事と、 GroovyでJSONをパースする(JsonSlurper) という記事を書きました。

それぞれ独立して紹介しても実際にどういう場合に使うの?というイメージが湧きづらいので、実際にRatpackを使って簡単なWebアプリを作ってみました。
このWebアプリは単純に、localhost:5050に、{id:1, name:koji}というようなJSONをPOSTで送信すると、そのJSONに現在時刻を追加してクライアントにJSONとして返す、というものです。

なお、Ratpackは標準ではJacksonを使ってJSONを取り扱います。
今回はJsonBuilderとJsonSlurperを使ったサンプルを書きたかったので利用していませんが、通常であればRatpackではJacksonを利用します。

RatpackはNonBlockingなWebフレームワークであると共に、とても軽量かつシンプルに処理を記述する事が出来ます。
またGroovyとの相性も抜群で、Groovyをインストールしていれば、以下のコードを適当なファイルに保存して、groovyコマンドで実行すればRatpackが起動してポート番号5050で処理を待ち受けるようになります。

ソース

@Grab('io.ratpack:ratpack-groovy:1.4.5')
@Grab('org.slf4j:slf4j-simple:1.7.12')
import static ratpack.groovy.Groovy.ratpack
import groovy.json.*

ratpack {
    handlers {
       post() {
          it.request.body.then() { 
             // Ratpackのメモ。request.getBody()はPromise<TypedData>を返してくれている 
             assert it instanceof ratpack.http.TypedData
    
             // 受け取ったJSON(テキスト)をJsonSlurperでパース 
             def gotJson = new JsonSlurper().parseText(it.text)
    
             // レスポンス用のJSONをJsonBuilderで生成 
             def json = new JsonBuilder()
             json([id: "${gotJson.id}", name:"${gotJson.name}", accessedDate: new Date().format("yyyy/MM/dd HH:mm:ss")])
    
             // クライアントにJSONを返す
             // 通常Jacksonを利用するのであれば、態々ヘッダーを指定する必要なし
             response.headers.set 'Content-Type', 'application/json'
             render json.toString()
          } 
       }
    }
}

このソースを適当にtest.grovyというような名前で保存して、groovy test.groovyと実行するだけでRatpackが起動します。

実行結果

[koji:~]$ curl -v http://localhost:5050/ -H "Content-type: application/json" -X POST -d '{"id": 1, "name": "koji"}'
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 5050 (#0)
> POST / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:5050
> Accept: */*
> Content-type: application/json
> Content-Length: 25
> 
* upload completely sent off: 25 out of 25 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 61
< connection: keep-alive
< 
* Connection #0 to host localhost left intact
{"id":"1","name":"koji","accessedDate":"2017/07/05 12:04:28"}%                                                                                                                                                                                                                  [koji:~]$ 

ちゃんとJSONを投げて、JSONを受け取ることが出来ました。
実際にJsonBuilderとJsonSlurperを利用している部分は

// 受け取ったJSON(テキスト)をJsonSlurperでパース 
def gotJson = new JsonSlurper().parseText(it.text)
    
// レスポンス用のJSONをJsonBuilderで生成 
def json = new JsonBuilder()
json([id: "${gotJson.id}", name:"${gotJson.name}", accessedDate: new Date().format("yyyy/MM/dd HH:mm:ss")])

の2行です。とってもシンプルですね。

このように、Groovyを利用することで簡単にJSONを扱うことが出来、さらに巨大なエコシステムを利用してRatpackのような素晴らしいフレームワークもお手軽に利用することが出来ることが分かりますね。

GroovyでJSONをパースする(JsonSlurper)

概要

JsonBuilderを使えば、Groovy上で簡単にJSONを生成できますが、同様に既存のJSON(テキスト)を解析してGroovyで扱う事も非常に簡単です。
そのためには、JsonSlurperというクラスを利用します。

使い方

本当に簡単なので以下のソースを見て頂ければ一目瞭然です。
単純にJsonSlurperインスタンスを生成して、そのparseTextメソッドにJSON(テキスト)を渡してあげるだけです。

import groovy.json.*
String jsonText = '''
{
    "name": "koji",
    "age": 32,
    "hobbies": [
        {
            "id": 1,
            "name": "hobby1"
        },
        {
            "id": 2,
            "name": "hobby2"
        }
    ],
    "address": {
        "country": "japan",
        "city": "tokyo"
    }
}
'''

def json = new JsonSlurper().parseText(jsonText)
assert json.name == 'koji'
assert json.age == 32
assert json.hobbies.size() == 2

// 配列(リスト)に入っている各オブジェクトにも普通にアクセスできる。
assert json.hobbies[0].id == 1
assert json.hobbies[0].name == 'hobby1'
assert json.hobbies[1].id == 2
assert json.hobbies[1].name == 'hobby2'

//オブジェクト内のオブジェクトにも当然普通にアクセスできる。
assert json.address.country == 'japan'
assert json.address.city == 'tokyo'

// 実は各オブジェクトは、Mapでは無くてJSON専用のLazyMapなので注意。
assert json.hobbies[0] instanceof groovy.json.internal.LazyMap
assert json.hobbies[1] instanceof groovy.json.internal.LazyMap
assert json.address instanceof groovy.json.internal.LazyMap

Webアプリを開発する際にWebフレームワークを使うのであれば、通常はリクエストから自動でJSONオブジェクトを生成するライブラリや機能が付属していると思いますので中々JsonSlurper自体を直接触る事はないかも知れませんが、どこかから提供されているデータがJSON形式の場合で、それを使ってサクッと何か計算なりをしたい場合には非常に役立つ機能だと思います。

参考:
JsonSlurper

Groovyの不思議なメソッド「call」

概要

通常のクラスであれば、インスタンスをnewで生成して、メソッドを呼び出します。

class Hoge {
    def test(String message) {
        "${message} in Hoge#test"
    }
}
def hoge = new Hoge()
assert hoge.test("test") == "test in Hoge#test"

Groovyのクロージャにも同様のものが有りますが、インスタンスメソッドにもcallという特殊な名前のものを定義することが出来ます。
このcallというメソッドを定義すると、メソッド名を省略してcallメソッドを実行できます。

使い方

実際にサンプルを見てみます。先ほどのHogeクラスにcallメソッドを追加したのが以下です。

class Hoge {
    def test(String message) {
        "${message} in Hoge#test"
    }
    
    // これを追加
    def call(String message) {
        "${message} in Hoge#call"
    }
}
def hoge = new Hoge()
assert hoge.test("test") == "test in Hoge#test"

// 当然普通に呼べる
assert hoge.call("test") == "test in Hoge#call"

// しかし!callメソッドはメソッド名を省略できる!
assert hoge("test") == "test in Hoge#call"

このようにcallメソッドを定義することで、インスタンスが格納されている変数自身がまるでメソッドのように振る舞うことが出来ます。
普段のコーディングで使いドコロがあるか、と言われると微妙ですが、DSLを自作する際などには威力を発揮するのではないでしょうか?

以下、自分の確認用に作成したMapで渡された値を元に簡単なHTMLを生成するサンプルです。

class MyHTMLBuilder {
    def call(Map htmlParts) {
        """
        <html>
        <head>${htmlParts.head.collect{k,v -> "<${k}>${v}</${k}>"}.join("")}</head>
        <body>${htmlParts.body.collect{k,v -> "<${k}>${v}</${k}>"}.join("")}</body>
        </html>
        """
    }
}

def html = new MyHTMLBuilder()
def map = ['head': ['title': 'hogehoge'], 'body':['h1': 'これはTitle','p':'Groovy良いよね!']]
println html(map)

実行結果は以下のようになります。(見やすいようにインデントしています)

<html>
    <head>
        <title>hogehoge</title>
    </head>
    <body>
        <h1>これはTitle</h1>
        <p>Groovy良いよね!</p>
    </body>
</html>    

GroovyでJSONを生成する(JsonBuilder)

GroovyのコードとしてJSONを生成する

特に難しい内容ではないのですが、自分で使う時に毎回ググっているので備忘録として纏めます。
JsonBuilderというGroovy標準のクラスを利用することで簡単にJSONを生成することが出来ます。

import groovy.json.*
def json = new JsonBuilder()
json(
    name: 'koji',
    age: 32,
    hobbies: [[id: '1', name:'hobby1'], [id: 2, name: 'hobby2']],
    address: [country: 'japan', city: 'tokyo']
)
println json.toPrettyString()

実行結果は以下の様になります。

{
    "name": "koji",
    "age": 32,
    "hobbies": [
        {
            "id": "1",
            "name": "hobby1"
        },
        {
            "id": 2,
            "name": "hobby2"
        }
    ],
    "address": {
        "country": "japan",
        "city": "tokyo"
    }
}

ぱっと見で分かるシンプルな例ですが、
nameageはそれぞれ普通の値です。
hobbiesは、オブジェクトを格納する配列です。
addressは、オブジェクトを格納するオブジェクトです。
また、nameageのように、Groovyが自動的に型を判断してダブルクオーテーションで囲んだりしてくれています。非常に楽ちんです。

公式ドキュメントには、JsonBuilderに対してjson{...}というサンプルが用意されていますが、それだと上記のようなhobbies配列にオブジェクトを渡すことが出来ません。(出来るとは思うのですが自分はその方法を知りません。。。)
そこで、JsonBuilderをjson(...)という形で呼び出すようにすれば、JSONの値はすべてMapで記述することが出来ます。

もう少し詳細

jsonに続いて書く内容は、すべてcallメソッドへ渡されて実行されます。
callメソッドは渡される型によっていくつかオーバロードされています。

json {...}は、JsonBuilder#call(Closure)が呼ばれ、json(...)JsonBuilder#call(Map)が呼ばれている形です。
少し混乱しそうですが、Groovyは引数としてに渡すMapの場合、最初の[ ]は省略できます。
そして、引数があるメソッドを実行する際には、引数の( )を省略できます。

まとめると、 json(...)は、json.call([:])を実行していて、json{}json.call({クロージャ})を実行している、という事になります。

慣れるまでは、なんだかよく分からん、という状態になりそうですが、基本的にはJSONにしたい値をすべてMapで表現しておいて、それをJsonBuilderに渡してあげれば問題ないと思います。

参考:
JsonBuilder

追記

callにクロージャを渡して、配列も定義できる方法を教えて頂きました。

教えて頂いたリンク先のコードは以下のようになっています。

import groovy.json.*
def json = new JsonBuilder()
json {
    name 'koji'
    age 32
    hobbies([id: '1', name:'hobby1'], [id: 2, name: 'hobby2'])
    address([country: 'japan', city: 'tokyo'])
}

println json.toPrettyString()

hobbiesのような配列のものも( )で囲めばイケるようです。

Grails3の本番環境で/dbconsoleにアクセスできるようにする

いつからか分からないけど、少なくともGrails 3.2.8では、production環境ではデフォルトで/dbconsoleにアクセスできなくなっています。(セキュリティ上の理由で)
閉じた環境だしSpringSecurityCoreとか使ってるから大丈夫!という場合は、application.ymlに以下の設定を追加すれば大丈夫。

environments:
    production: # これはもともとapplication.ymlにある。この下に以下の3行を追加。
        grails:
            dbconsole:
                enabled: true

これで再起動すればOK。