GDataAPIを使って はてなのAtomPubを操作する方法

AppEngineからはてなのAtomPubAPIにアクセスしたくて実装方法を検討しました。

Abdera Client に嵌る

最初、Apache Abdera Project というのを見つけてこいつは便利そうだ、WSSE認証もついてる、と本命視。
しかし、commons httpclientを使っていてAppEngine上では動かない。
commons httpclientがAppEngine上で動いたという記事もあり惑わされつついろいろやってみるも結局断念。

嵌り期間、約2週間 orz..

GData API を拡張して WSSE認証に対応させる

次に、Google GData APIGoogleサービス向けのAtomPub実装なので筋はよさそう。しかしWSSE認証がついていないのでその部分をフックできればいけるのではないか・・・

WSSE認証については、id:tomorrowkey さんのエントリ はてなブックマークAtomAPI - 明日の鍵 を参考にしました。(Thx)

GDataのRequestに WSSEヘッダを追加してやるために com.google.gdata.client.Service を拡張して createRequestメソッドをoverrideします。

public class HatenaAtomService extends Service {
    
    private String wsseHeader = null;
    
    public void setWSSEHeader(String username, String password) {
        try {
            this.wsseHeader = this.getWSSEHeaderValue(username, password);
        } catch (NoSuchAlgorithmException e) {
        } catch (UnsupportedEncodingException e) {
        }
    }

    @Override
    public GDataRequest createRequest(GDataRequest.RequestType type,
            URL requestUrl, ContentType inputType) throws IOException,
            ServiceException {

        GDataRequest request = super.createRequest(type, requestUrl, inputType);
        request.setHeader("X-WSSE", wsseHeader);
        return request;
    }

    private String getWSSEHeaderValue(String user, String password)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        byte[] nonceB = new byte[8];
        SecureRandom.getInstance("SHA1PRNG").nextBytes(nonceB);
        SimpleDateFormat zulu =
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        zulu.setTimeZone(TimeZone.getTimeZone("GMT"));
        Calendar now = Calendar.getInstance();
        now.setTimeInMillis(System.currentTimeMillis());
        String created = zulu.format(now.getTime());
        byte[] createdB = created.getBytes("utf-8");
        byte[] passwordB = password.getBytes("utf-8");

        byte[] v = new byte[nonceB.length + createdB.length + passwordB.length];
        System.arraycopy(nonceB, 0, v, 0, nonceB.length);
        System.arraycopy(createdB, 0, v, nonceB.length, createdB.length);
        System.arraycopy(
            passwordB,
            0,
            v,
            nonceB.length + createdB.length,
            passwordB.length);

        MessageDigest md = MessageDigest.getInstance("SHA1");
        md.update(v);
        byte[] digest = md.digest();

        StringBuffer buf = new StringBuffer();
        buf.append("UsernameToken Username=\"");
        buf.append(user);
        buf.append("\", PasswordDigest=\"");
        buf.append(new String(Base64.encodeBase64(digest)));
        buf.append("\", Nonce=\"");
        buf.append(new String(Base64.encodeBase64(nonceB)));
        buf.append("\", Created=\"");
        buf.append(created);
        buf.append('"');
        return buf.toString();
    }
}

たったこれだけでFeedの取得からEntryの投稿まで簡単にできるようになりました。(Abderaで嵌った時間はなんだったのだ^^;)

使い方

上記 HatenaAtomService を適当にどっかに作成してください。

  • Feed取得
    HatenaAtomService service = new HatenaAtomService();
    service.setWSSEHeader("ユーザ名", "パスワード");
    Feed f = service.getFeed(new URL(url), Feed.class);
  • エントリ投稿
    HatenaAtomService service = new HatenaAtomService();
    service.setWSSEHeader("ユーザ名", "パスワード");

    BlogEntry e = new BlogEntry();
    e.setTitle(new PlainTextConstruct("GData API から投稿してみるテスト"));
    e.setContent(new TextContent(new PlainTextConstruct("はてな記法で。\n- はてな\n- 記法")));
    service.insert(new URL(url), e);