読者です 読者をやめる 読者になる 読者になる

commonsのHttpClientを試してみた

3.1は古いけど、libに入っていたので3.1の呼び方を確認。

@Grab('commons-httpclient:commons-httpclient:3.1')
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;

HttpMethod method = new GetMethod("http://www.google.com/");
try {
    HttpClient client = new HttpClient();

    client.executeMethod(method);
    return method.getResponseBodyAsString()
} finally {
    method.releaseConnection();
}

Resultにgoogleのトップページのhtmlが入ってた。よしよし。

GroovyでJAXB使うときには@XmlAccessorType( XmlAccessType.NONE ) する

JAXBの感触を確かめようとGroovyで弄ってみたら、ちょこっとハマりました。
さらっとバインディング対象のクラスを書いてマーシャリングしてみようって以下のようなコードを書いたんですが、

import javax.xml.bind.annotation.*
import javax.xml.bind.*

@XmlRootElement
class Foo {
    @XmlAttribute
    String id

    @XmlAttribute
    String name
}

JAXBContext.newInstance(Foo.class).createMarshaller().with {
    setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    
    marshal(new Foo(id:'100', name:'foofoo'), System.out)
}

エラーになります。

com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
groovy.lang.MetaClass is an interface, and JAXB can't handle interfaces.
	this problem is related to the following location:
		at groovy.lang.MetaClass
		at public groovy.lang.MetaClass Foo.getMetaClass()
		at Foo
groovy.lang.MetaClass does not have a no-arg default constructor.
	this problem is related to the following location:
		at groovy.lang.MetaClass
		at public groovy.lang.MetaClass Foo.getMetaClass()
		at Foo
Class has two properties of the same name "id"
	this problem is related to the following location:
		at public java.lang.String Foo.getId()
		at Foo
	this problem is related to the following location:
		at private java.lang.String Foo.id
		at Foo
Class has two properties of the same name "name"
	this problem is related to the following location:
		at public java.lang.String Foo.getName()
		at Foo
	this problem is related to the following location:
		at private java.lang.String Foo.name
		at Foo

idというプロパティが2つ?nameという名前のプロパティが2つ??MetaClassが悪さしてるの???ややこしそうなエラーでした。
で、ちょっと調べてみると…
http://stackoverflow.com/questions/1161147/how-do-i-get-groovy-and-jaxb-to-play-nice-together

The reason it is not working is that each Groovy Class has a metaClass property on it. JAXB is trying to expose this as a JAXB property which obviously fails. Since you don't declare the metaClass property yourself, it is not possible to annotate it to have JAXB ignore it. Instead you and set the XmlAccessType to NONE.

〜中略〜

Example:

@XmlAccessorType( XmlAccessType.NONE )
@XmlRootElement
public class PlayerGroovy {
    @XmlAttribute
    String value
}

「@XmlAccessorType( XmlAccessType.NONE )」て付けてやればmetaClassをマーシャリングしに行かなくなるから良いよって事でした。
早速やってみると…

import javax.xml.bind.annotation.*
import javax.xml.bind.*

@XmlAccessorType( XmlAccessType.NONE )
@XmlRootElement
class Foo {
    @XmlAttribute
    String id

    @XmlAttribute
    String name
}

JAXBContext.newInstance(Foo.class).createMarshaller().with {
    setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    
    marshal(new Foo(id:'100', name:'foofoo'), System.out)
}

実行してみると…

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><foo name="foofoo" id="100"/>

動いた!てわけで、@XmlAccessorType( XmlAccessType.NONE )重要です。

この@XmlAccessorTypeの記述、XmlTypeにも必要でした。

import javax.xml.bind.annotation.*
import javax.xml.bind.*

@XmlRootElement
@XmlAccessorType( XmlAccessType.NONE )
class Foo {
    @XmlAttribute
    String id
    
    @XmlAttribute
    String name
    
    @XmlElementWrapper
    @XmlElement(name='bar')
    List<Bar> bars = []
}

@XmlType
@XmlAccessorType( XmlAccessType.NONE )
class Bar {
    @XmlAttribute
    String name
}

JAXBContext.newInstance(Foo.class).createMarshaller().with {
    setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    
    marshal(new Foo(id:'100', name:'foofoo', bars:[new Bar(name:'barbar1'), new Bar(name:'barbar2')]), System.out)
}

こんな感じ。

MacBook Air 11インチ欲しい!

MacBook Air 11インチ欲しい!キャンペーンに参加です。
13インチがもうあるじゃないかと各方面からツッコミをもらったけど、やっぱり欲しいのです。だって11.6インチならボディバックとかにも入りそうじゃ無いですか。13インチが微妙にはみ出すので使えなくなったお気に入りのあのPorterのカバンもきっと入りますし。
メモリは4GB欲しいけど…CPUも上のが良いけど…もらえるなら贅沢は言いません。あたらないかなぁ…

GrabでJDBCドライバを読み込めない

Groovyのjiraにもあがっているのですが、GroovySQLを使うときにGrabでドライバを取ってこようとして

@Grab('postgresql:postgresql:8.4-701.jdbc3')
import groovy.sql.Sql

Sql.newInstance('jdbc:postgresql://localhost:5432/database',
                'user',
                'password',
                'org.postgresql.Driver'
).with {
    eachRow("select * from table", {println it})
}

とやってもjava.lang.ClassNotFoundExceptionが発生してしまいます。JDBCドライバをシステムクラスローダに探しに行ってしまって、Grabで入れたクラスは見つからないのです。
…と、このあと回避法を書いてたんですが、1.7-beta2で入ったらしい正しい対処法をコメントで教えてもらいました!

GrabConfigでシステムクラスローダに追加するようにできますよ。

@Grapes([
    @Grab('postgresql:postgresql:8.4-701.jdbc3'),
    @GrabConfig(systemClassLoader=true)
])

1.7使ってるんならこうすりゃええんですね。ふむふむ。

てわけで、以下みたいな裏ワザはいりませんでした。まぁなんかの理由で1.6以前のGroovy使わなあかん時には必要なので、一応残しときます。
てもGrabの位置が1.7仕様なのでそのままでは使えませんが…

そこで、Sql.newInstanceとする前に

this.class.classLoader.getURLs().each{
    ClassLoader.systemClassLoader.addURL(it);
}

としてやると、うまく動きます。
または、DataSourceを使ってSqlインスタンスを明示的にnewしても動きます。

@Grab('postgresql:postgresql:8.4-701.jdbc3')
import org.postgresql.ds.PGSimpleDataSource
import groovy.sql.Sql

new Sql(new PGSimpleDataSource(
        serverName: 'localhost',
        databaseName: 'database',
        user: 'user',
        password: 'password'
)).with {
    eachRow("select * from table", {println it})
}

次にはまらないためのメモ。

動的メソッド追加の謎

[GROOVY-3674] In Closure as injected method of Closure, delegate is not collect - jira.codehaus.orgを見て、少し実験してみました。
Javaのクラスの場合、最初はメタクラスがないからグローバルなやつを使うんだよ、との事で。つまりメタクラス拡張を行った後だとStringでも例外が発生する?ぽい感じです。
で、やってみると…

String str = 'A'

String.metaClass.test = {
    assert delegate instanceof String
    assert delegate == str
}

str.test()

は動きますが、

String str = 'A'

str.metaClass.getFoo = {return "foo"}

String.metaClass.test = {
    assert delegate instanceof String
    assert delegate == str
}

str.test()

とすると

Exception thrown: No signature of method: java.lang.String.test() is applicable for argument types: () values: []

groovy.lang.MissingMethodException: No signature of method: java.lang.String.test() is applicable for argument types: () values: []
	at ConsoleScript0.run(ConsoleScript0:10)

のように例外が発生します。ここまでは書いてある通りです。なるほどねー。
…ただし、まず1個目を実行した後GroovyConsoleを開いたままで2個目を実行すると例外が起きません!
さらに、2回目で

String str = 'B'

str.metaClass.getFoo = {return "foo"}

String.metaClass.test = {
    assert delegate instanceof String
    assert delegate == str
}

str.test()

とstrの値を変えて実行すると…

Exception thrown: Expression: (delegate == str). Values: delegate = B, str = A

java.lang.AssertionError: Expression: (delegate == str). Values: delegate = B, str = A
	at ConsoleScript1$_run_closure2.doCall(ConsoleScript1:7)
	at ConsoleScript1$_run_closure2.doCall(ConsoleScript1)
	at ConsoleScript2.run(ConsoleScript2:10)

なんと!delegateが呼び出し対象のオブジェクトとは違うオブジェクトを指しているではありませんか!
なんともトリッキーな…てか、これは仕様バグと言って良いのではないでしょうか。
まぁ、Javaのオブジェクトにメタクラス拡張をかけるのは非常に危険だ、という事ですね。おそろしや…

Closureにメソッド足すにはExpandoMetaClass.enableGlobally()が必要

これはまぁ、{}でクロージャ作るたびにClosureのサブクラスが一つできるので当たり前なんですけど、エラーがちょっとわかりにくいんですね。メソッドが呼べない訳ではなくて、delegateが適切にセットされなくなる。
なんでdelegateを使わないメソッドを足す分にはExpandoMetaClass.enableGlobally()しなくても動いちゃったりする。ちょっとトリッキーな気がします。
[GROOVY-3674] In Closure as injected method of Closure, delegate is not collect - jira.codehaus.orgを読むと説明してあるんですけど…ちょっと英語の読解力がついていきませんでした^^;
ま、とにかく本来動かないものがなんとなく呼べちゃってるけど、ExpandoMetaClass.enableGlobally()しないとClosureクラスへの拡張は個々のクロージャには適切に反映されません。別クラスだし。
…位に理解しときゃいいのかなぁ?と思います。


ただし、ExpandoMetaClass.enableGlobally()すると劇重になるらしいので、ご注意ください。動的メソッドの検索範囲が一気に増えますから、そりゃ、ねぇ。
どっかで読んだだけで検証した訳じゃないのでもしかしたらもうそんな重くないかもしれませんが、まぁ博打ですよね。やっちゃってどうなっても私はしりませんよっと;-p

Closureリテラルのメンバにアクセスするときは、前の行にセミコロンが要る

ちょっと意味不明ですね。後置ifの例で行きます。

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

なんか、セミコロンが不自然に入ってますよね?これ、要るんです実は。これつけないと、なんか前の文と引っ付いちゃったりするんですね。

def executed = false

{->executed = true}.if('a' == 'A')

なんて書くと、

def executed = false{->executed = true}.if('a' == 'A')

と認識されて…

def executed = false({->executed = true}).if('a' == 'A')

てなっちゃいます。

[1,2,3].collect{it+3}.each
    {println it}

て書けるため…てんな書き方するか!…まぁ改行も空白文字の1つって扱いなので

[1,2,3].collect {it+3} .each {println it}

みたいな書き方を許容するため、でしょうね。