2012年7月24日火曜日

Lightweight XMLSchema dateTime parser in Java

To start with the conclusion, XMLSchemaDateTimeParser.parse(String) does the job.

String dateTime = "2005-11-14T02:16:38Z";
Calendar calendar = XMLSchemaDateTimeParser.parse(dateTime);

The XMLSchema specification defines some built-in data types. 'dateTime' is one of them and is a subset of ISO 8601. The format of XMLSchema dateTime is defined as follows.

yyyy-mm-ddThh:mm:ss{milliseconds}{timezone}

{milliseconds} part can be omitted. If it exists, it should follow the format: '.' s+ (one leading period plus one or more digits). One example is ".125" and this means 125 milliseconds.

{timezone} part also can be omitted. If it exists, it should be either a single 'Z' or a timezone offset written in this format: [+-]hh:mm. For example, "+09:00" indicates 9 hours ahead of Coordinated Universal Time (UTC). 'Z' means offset zero (±00:00).

Strictly speaking, the specification allows a leading hyphen ('-') before the year part (yyyy) as a negative sign for B.C. years, but I removed it from the format shown above for brevity.

Below are examples of strings written in the XMLSchema dateTime format.

  • 2005-11-14T02:16:38Z
  • 2005-11-14T02:16:38+09:00
  • 2005-11-14T02:16:38.125

Joda Time (Java date and time API) is a well-known open-source Java library that can parse ISO 8601 just as one of its functions, but if what you want to do is just to parse XMLSchema dateTime, it may be too big. In such a case, consider to use XMLSchemaDateTimeParser (hosted on GitHub) which consists of just one source file (XMLSchemaDateTimeParser.java).

However, note that XMLSchemaDateTimeParser has some (trivial) limitations as listed below.

  1. The specification allows the year part to be a 5-or-mode digit number, but XMLSchemaDateTimeParser assumes only 4-digit numbers at the year part.
  2. The specification allows a leading hyphen ('-') to indicate B.C. years, but XMLSchemaDateTimeParser just ignores the negative sign.
  3. The specification does not mention the length of the millisecond part, but XMLSchemaDateTimeParser assumes that the length is between 1 and 3.


軽量 XMLSchema dateTime パーサー (Java)

結論から先に言うと、XMLSchemaDateTimeParser.parse(String) で作業完了です。

String dateTime = "2005-11-14T02:16:38Z";
Calendar calendar = XMLSchemaDateTimeParser.parse(dateTime);

XMLSchema の仕様では、組み込みのデータ型を幾つか定義しています。'dateTime' はその一つであり、ISO 8601 のサブセットとなっています。XMLSchema dateTime のフォーマットは次のように定義されています。

yyyy-mm-ddThh:mm:ss{ミリ秒}{タイムゾーン}

{ミリ秒} の部分は省略可能です。存在している場合は、「'.' s+ (一つのピリオドと一つ以上の数字)」というフォーマットとなります。例えば ".125" であれば、125ミリ秒を意味します。

{タイムゾーン} の部分も省略可能です。存在している場合は、'Z' 一文字か、もしくは「[+-]hh:mm」というフォーマットでタイムゾーン・オフセットを示します。例えば "+09:00" は協定世界時 (UTC) より 9 時間進んでいることを示します。'Z' はオフセット・ゼロ (±00:00) を意味します。

厳密に言うと、仕様によれば、年の部分 (yyyy) の前にハイフン ('-') を付けて紀元前を示すことが可能ですが、分かりやすくするために上述のフォーマットからは省いています。

下記は XMLSchema dateTime フォーマットで書かれた文字列の例です。


  • 2005-11-14T02:16:38Z
  • 2005-11-14T02:16:38+09:00
  • 2005-11-14T02:16:38.125

Joda Time (Java date and time API) という良く知られたオープンソースの Java ライブラリがあり、機能の一つとして ISO 8601 のパースも可能ですが、やりたいことが XMLSchema dateTime のパースだけであれば、Joda Time ライブラリは大き過ぎるかもしれません。そのような場合は、単一のソースファイル (XMLSchemaDateTimeParser.java) だけで構成される XMLSchemaDateTimeParser (GitHub で公開) の使用を検討してみてください。

但し、XMLSchemaDateTimeParser には幾つか (些細な) 制限があります。

  1. 仕様では年の部分を 5 桁以上の数字にすることを許しているが、XMLSchemaDateTimeParser では年の部分には 4 桁の数字のみを想定している。
  2. 仕様では紀元前を示すために先頭にハイフンを付けることを許しているが、XMLSchemaDateTimeParser は、当該負号を無視する。
  3. 仕様ではミリ秒部分の長さに言及していないが、XMLSchemaDateTimeParser では長さが 1 から 3 の間であると想定している。


2012年7月18日水曜日

NoNode for /ledgers/cookies error by bookkeeper

If you encounter an error like below when you run "bin/bookkeeper bookie",

org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /ledgers/cookies
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:111)
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
        at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:778)
        at org.apache.bookkeeper.bookie.Cookie.writeToZooKeeper(Cookie.java:136)
        at org.apache.bookkeeper.bookie.Bookie.checkEnvironment(Bookie.java:312)
        at org.apache.bookkeeper.bookie.Bookie.<init>(Bookie.java:352)
        at org.apache.bookkeeper.proto.BookieServer.newBookie(BookieServer.java:82)
        at org.apache.bookkeeper.proto.BookieServer.<init>(BookieServer.java:75)
        at org.apache.bookkeeper.proto.BookieServer.main(BookieServer.java:300)

Try to type the following commands and run "bin/bookkeeper bookie" again.

$ bin/bookeeper org.apache.zookeeper.ZooKeeperMain \
    -server <server>:<port> create /ledgers 0
$ bin/bookeeper org.apache.zookeeper.ZooKeeperMain \
    -server <server>:<port> create /ledgers/available 0

Replace <server> and <port> with your ZooKeeper server and its port. myzookkeeper:2181, for example.

-----
「bin/bookkeeper bookie」を実行したときに下記のようなエラーが発生したら、

org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /ledgers/cookies
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:111)
        at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
        at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:778)
        at org.apache.bookkeeper.bookie.Cookie.writeToZooKeeper(Cookie.java:136)
        at org.apache.bookkeeper.bookie.Bookie.checkEnvironment(Bookie.java:312)
        at org.apache.bookkeeper.bookie.Bookie.<init>(Bookie.java:352)
        at org.apache.bookkeeper.proto.BookieServer.newBookie(BookieServer.java:82)
        at org.apache.bookkeeper.proto.BookieServer.<init>(BookieServer.java:75)
        at org.apache.bookkeeper.proto.BookieServer.main(BookieServer.java:300)

次のコマンドをタイプし、もう一度「bin/bookkeeper bookie」を実行してみてください。

$ bin/bookeeper org.apache.zookeeper.ZooKeeperMain \
    -server <server>:<port> create /ledgers 0
$ bin/bookeeper org.apache.zookeeper.ZooKeeperMain \
    -server <server>:<port> create /ledgers/available 0


<server><port> は、ZooKeeper サーバーとそのポートに置き換えてください。例えば、myzookeeper:2181 など。

2012年7月13日金曜日

asm-3.1.jar; error in opening zip file

I encountered a Maven error caused by asm-3.1.jar. The error message was saying:

error: error reading /root/.m2/repository/asm/asm/3.1/asm-3.1.jar; error in opening zip file

I found soon that asm-3.1.jar in .m2 was not a (corrupt) jar file but an HTML file whose content was as follows:

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/0.6.39</center>
</body>
</html>

It seems 'mvn' cannot handle '301 Moved Permanently' properly.

So, I downloaded asm-3.1.jar from the central repository,

central / asm / asm / 3.1
http://search.maven.org/#browse|-447836703
http://search.maven.org/remotecontent?filepath=asm/asm/3.1/asm-3.1.jar

and copied it to the local repository.

$ cp asm-3.1.jar ~/.m2/repository/asm/asm/3.1/asm-3.1.jar
$ rm ~/.m2/repository/asm/asm/3.1/asm-3.1.pom

This worked in my case.


Edit: 2012/11/14

Probably, a better way than 'cp' is to use 'mvn install:install-file'. After downloading asm-3.1.pom, then type:

$ mvn install:install-file -Dfile=asm-3.1.jar -DgroupId=asm -DartifactId=asm -Dversion=3.1 -Dpackaging=jar -DpomFile=asm-3.1.pom

See also:

Installing an artifact to a specific local repository path
http://maven.apache.org/plugins/maven-install-plugin/examples/specific-local-repo.html

2012年7月6日金曜日

Twitter OAuth View for Android using twitter4j

TwitterOAuthView is a WebView subclass dedicated to Twitter OAuth on Android.

    GitHub TwitterOAuthView
        https://github.com/TakahikoKawasaki/TwitterOAuthView
        [Javadoc] http://takahikokawasaki.github.com/TwitterOAuthView/index.html

Since it is implemented as a subclass of View, it can be integrated into the Android layout system seamlessly. This fact makes TwitterOAuthView an easily-reusable UI component.

Its usage is very simple. Just call start() method and receive its result via TwitterOAuthView.Listener.

    // Start Twitter OAuth process. Getting a request token, opening Twitter's
    // authorization page, and getting an access token are performed.
    view.start(CONSUMER_KEY, CONSUMER_SECRET, CALLBACK_URL, true, listener);

    // Definition of TwitterOAuthView.Listener interface.
    void onSuccess(TwitterOAuthView view, AccessToken accessToken);
    void onFailure(TwitterOAuthView view, TwitterOAuthView.Result result);

An example of Activity implementation using TwitterOAuthView can be found at GitHub TwitterOAuthView.

---

TwitterOAuthView は、Android での Twitter OAuth に特化した WebView のサブクラスです。

    GitHub TwitterOAuthView
        https://github.com/TakahikoKawasaki/TwitterOAuthView
        [Javadoc] http://takahikokawasaki.github.com/TwitterOAuthView/index.html

View のサブクラスとして実装されているので、Android のレイアウトシステムにシームレスに統合することができます。この事実により、TwitterOAuthView は簡単に再利用できる UI 部品となっています。

使い方はとてもシンプルです。start() メソッドを呼び、結果を TwitterOAuthView.Listener インターフェースで受け取るだけです。

    // Twitter OAuth プロセスを開始します。リクエストトークンの取得、Twitter 認証
    // ページのオープン、アクセストークンの取得が実行されます。
    view.start(CONSUMER_KEY, CONSUMER_SECRET, CALLBACK_URL, true, listener);

    // TwitterOAuthView.Listener インターフェースの定義
    void onSuccess(TwitterOAuthView view, AccessToken accessToken);
    void onFailure(TwitterOAuthView view, TwitterOAuthView.Result result);

TwitterOAuthView を用いた Activity 実装の例は GitHub TwitterOAuthView にあります。