Category Archives: Java

JJUG ナイト・セミナー 「ビール片手にLT&納涼会」でLTしてきました。

動画:

こちらが自分で

こちらが共同発表をしてくれた@smilelx_xl さんです。

去年の11月にCCCで発表させて頂いて以来、公の場で発表するのは二度目でした。共同発表をしてくれた、 @smilelx_xl さんは初めての発表だったようですが、手書き風スライドが好評でした。
一方僕の発表はビデオで見ると早口過ぎて聞き取れない、スライドは文字が小さすぎて読めないという相当ダメな内容で、改善の余地があることが分かりました。自分の発表をビデオで見るということは非常に勉強になり、撮影・youtubeアップいただいた、@yusuke さんには大変感謝致します。

今回の発表で伝えたかったことは、デザイナーさん視点の声でした。僕の発表は、@smilelx_xl さんがスムーズに発表できるための前置き的位置づけだと考えれば十分目的を達成できたのではないかと思います。
スライドで書いたように、2009年からMayaaを使い始め、この発表で扱われていることは、2010年から2012年頃の話です。その頃は自分の所属する会社も小さく従業員は一桁でした。プログラマーといえば、社員は僕ともう一人、Webデザイナーさんは@smilelx_xl さん一人でした。サービスを成長させる過程は面白いものですが、このテンプレートエンジンのこと一つとっても、世の中にシェアしたいと常々思っていました。ただ、多分僕一人出て行ってもインパクトがないので、@smilelx_xl さんに発表させたいと周囲に話したりしていました。もし、Seasar Conferenceが2011年以降も行われていたら、もっと早く応募して発表が実現していたかもしれません。JJUGはちょっと固いイメージがあったので発表するには敷居が高く感じていました。そんなJJUGも最近世代交代が行われたため、急に近寄りやすくなったと感じています。お陰で今回の発表を応募することができたと思います。

Twitterで「デザイナーは名前空間なんて言わないだろ」という的確なつっこみを頂きましたが、ごもっともです。実は今回の発表は、僕が事前に原稿案を作成し、@smilelx_xlさんに渡していました。@smilelx_xlさんはそのまま書かれたのですね。まったくもって「一般人に分かりやすい」用語を使っていない一例です。
今回はデザイナーとプログラマーの話でしたが、今回の話の概要は、実は、別の組み合わせでも成り立つと思います。違う職種同士共同作業をする上で大切なことは同じだと思います。一人で全部できてしまうスーパーエンジニアなら、もっと効率がよいかもしれませんし、実際にそういうスキルがある人も知っていますが、普通はなかなかいないでしょう。それに、やはり、違うバックグラウンドをもった人同士が共同作業をすることは楽しい。

そういうわけで「Aを利用してプログラマーとBが共同作業をする上で大切なこと」シリーズをやったらどうかと思いましたが、毎回僕が社内の別部署の女子を連れて発表したらさすがに殺されると思うので、うちのチームの誰かやってくれないかなーと思っていない(笑)

これでこの件の発表はおしまいと思ったのですが、尊敬する@skrbさんに、JJUG CCC 2013 Fall の Call for Paperに応募しないかとはっぱをかけられてしまったので、今回の発表をもっと詳細化したバージョンをまた@smilelx_xlさんと共同で計画しています。もし、採用されたら今度は早口にならずに丁寧に話したいと思います。

JavaMailで文字化けを防ぐ方法まとめ

ハマったので自分のためにまとめます。
JavaMailで普通にメールを送信すると「〜」や丸数字が文字化けします。
その対策についての調べた記事をまとめます。

x-windows-iso2022jpを使う

http://www.igapyon.jp/igapyon/diary/2007/ig070427.html

Shift_JISに対するWindows31Jみたいなコードです。これを入れると文字化けが解消します。しかし、ユーザエージェント側がx-windows-iso2022jpを知らないという問題があり、下記Javaの起動オプションで回避するという手法が紹介されています。

-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP

文字コードを自分でエスケープしてしまおうという方法

http://www.sk-jp.com/cgi-bin/treebbs.cgi?kako=1&log=1430

JavaMailのエンコード機能を使用せず自前でエンコードする方法です。この方法を使えば起動オプションを使用しなくても、文字化けなく「〜」や丸数字を送ることができます。

ここで紹介されているコードでは半角カナを全角カナに変換しています。

半角カナはどうしても半角カナのままで送りたく、起動オプションを追加することもできない場合どうするか?

http://www.siisise.net/charset.html
例えば「ESC(I」というエスケープコードを送信すると、半角カナに対応することができます。
自分でエンコードする時、半角カナ文字が来たらこのエスケープ文字を送信するようにすれば文字化けなく半角カナメールを送ることができます。

ここを見ると、JIS X 2013なども含まれています。
実際のメーラが受信できるかはわかりませんが、拡張漢字も対応できるようです。

RFCには存在しないコードなので、自己責任で対応する必要があります。

問題点(2012/11/11 追記)

上記のように非標準な文字コードを使った実害として、Macを中心に全文が文字化けしてしまうという現象があるようです。下記は標準のMail.appの特定のバージョンで発生していましたが、今はパッチがリリースされているのかな?また、Sparrowを使っている人からも、同様の報告を受けました。
https://discussionsjapan.apple.com/thread/10116033?start=0&tstart=0

このように非標準の文字コードを使った罰は「たった一文字化け内容に気をつけたために、全部の文字が化けてしまう」という報いとして現れるようです。

システム開発者としては、上記の問題点を顧客に説明した上で、下記のいずれかを採用するのが良いのでしょう。

  1. 標準ISO-2022-jpを使用、半角カナは全角カナに変換、いわゆる機種依存文字は使用しない
    • 使えない文字が多いけど一番確実にメールを送れる
  2. 上記の野良ISO-2022-jpを使用
    • 携帯に送るなら最適か?絵文字も送れるかも
  3. UTF-8メールを使用する
    • ガラケーや古いPCメーラを諦めなければならないけど、送れる文字では一番多い

以上

Mayaaでm:idの解決の仕方を自分好みにカスタマイズする方法

このブログで何度か触れましたが、僕の勤務先の会社ではMayaaを使っています。まだMayaaを使っていますし、これからも使っていくと思います。しかしさすがにMayaa長いこと使用していると、次の悩みが発生しました。

  • default.mayaaファイルが巨大化しすぎた
  • 利用当初のノウハウが無かった頃に作ったm:id体系を改めて新しく作り直したい!

しかしながら、

  • 既存の資産を捨てる訳にはいかない

というビジネス上の事情もあり、このような手段を取ることにしました。

  • 今までのID体系をm:id属性として提供し、新しいID体系をe:idとして提供する(eは弊社の製品のイニシャルがeであるためです)
  • e:idは全てdefault.mayaaのように全ページで使えるようにし、ファイルが肥大化しないように分割できるようにする
  • Mayaaファイル内の記述が冗長にならないよう、よく使うプロセッサーをショートカット出来るような新しいプロセッサーを作る

これらについての実現方法を今日は紹介したいと思います。ボリュームが多いので複数回に分けようと思います。

Not only m:id, but also e:id

テンプレートのid、またはm:id属性と、mayaaファイルのプロセッサーとのマッピングは、EqualsIDInjectionResolverによって行われています。他に、XPathMatchesInjectionResolverなど、複数のInjectionResolverが存在しそれらを登録することで、柔軟にプロセッサーの解決ルールを定義することができます。なんて柔軟な作りなのでしょう!このおかげで独自のInjectionResolverを実装して登録することによって、独自のルールでプロセッサーを解決することが出来るのです!この時点で勝利が決定したようなものです。

では、InjectionResolverの実装はどのようにすればいいでしょうか?実際はEqualsIDInjectionResolverのコードを熟読したわけですが(美しいコードで読みやすかったです!)、今回はEqualsIDInjectionResolverを継承することにしました。修正部分はこの部分です。

まず、IDをm:idではなく独自のnamespaceの属性で取れるようにします。

// ここにURIを定義、実際は所属組織のURIなどを記述
public static final URI URI_EXAMPLE_COM = URIImpl.getInstance("http://hogehoge.example.com");

@Override
protected NodeAttribute getAttribute(SpecificationNode node) {
    NodeAttribute attr = node.getAttribute(QNameImpl.getInstance(URI_EXAMPLE_COM, "id"));
    if (attr != null) {
    return attr;
    }
    return null;
}

これで、テンプレートに

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://mayaa.seasar.org" xmlns:e="http://hogehoge.example.com" xml:lang="ja" lang="ja">

と記述するだけで、m:idではなくe:idでマッピングすることができます。さらに、デフォルトのページ名.mayaaやdefault.mayaaを見に行かず別のルーティングでmayaaファイルを見つけに行くようにしましょう。これにはどうしたらよいでしょ?ヒントはページのmayaaファイルの次にdefault.mayaaを読みに行く機構です。親のMayaaファイルを探しに行く機構として、ParentSpecificationResolverというインターフェースが提供されています。

public interface ParentSpecificationResolver extends ParameterAware {
    /**
     * 指定した{@link Specification}の親を取得する。
     * <p>
     * 標準の実装では、テンプレートファイルの場合は対応するMayaaファイル、
     * Mayaaファイルの場合はdefault.mayaaファイルの{@link Specification}を返す。
     * default.mayaaの親はないので{@code null}を返す。
     * </p>
     * @param spec 親を探す起点となる{@link Specification}。見つからない場合は{@code null}。
     */
    Specification getParentSpecification(Specification spec);
}

ふむふむ。テンプレートもSpecificationであって、Mayaaファイルに当たるものはPageらしいです。そして、Pageの親はEngine(これはdefault.mayaaに相当)なのですね!

今回は、一つのInjectionResolverにだけ別の親解決ロジックを組み込みたかったので、残念ながらこの機構は使用出来ませんでした。それではどうするかというと、少々強引ですが、メソッド一個をコピペして書き換えました。

@Override
    public SpecificationNode getNode(SpecificationNode original,
        InjectionChain chain) {
    if (original == null || chain == null) {
        throw new IllegalArgumentException();
    }
    String id = getID(original);
    if (StringUtil.hasValue(id)) {
        Specification spec = SpecificationUtil.findSpecification(original);
        SpecificationNode injected = null;
        while (spec != null) {
        SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
        if (mayaa != null) {
            List injectNodes = new ArrayList();
            getEqualsIDNodes(mayaa, id, injectNodes);
            if (injectNodes.size() > 0) {
            injected = (SpecificationNode) injectNodes.get(0);
            if (isReportDuplicatedID() && injectNodes.size() > 1) {
                logWarnning(id, original, 2);
            }
            break;
            }
        }
// ここを標準と差し替える。 by ishigami
//             spec = EngineUtil.getParentSpecification(spec);
        spec = MyMayaaEngineUtil.getNextEIDSpecification(spec);
// ここを標準と差し替える。 by ishigami end
        }
        if (injected != null) {
        if (QM_IGNORE.equals(injected.getQName())) {
            return chain.getNode(original);
        }
        return injected.copyTo(getCopyToFilter());
        }
        if (isReportResolvedID()) {
        logWarnning(id, original, 1);
        }
    }
    return chain.getNode(original);
    }

願うことならこの部分がprotected以上のメソッドとして提供されていたらOverrideできたので、是非ともそのようになって欲しいですね。

さて、MyMayaaEngineUtil.getNextEIDSpecification(spec);についてですが、
これは、ディレクトリのリストを取得して、アルファベット順に次のmayaaファイルを返し、次のファイルが無くなったらEngineを返すようにしています。普通のコードなのでここでは割愛します。

さあ、これで、任意のディレクトリに置いたmayaaファイルを順に読みこんでくれるようになったので、ディレクトリにmayaaファイルを分割して配置することが出来るようになりました!全部ロードするのが不都合になったらのちのちimport機構を作ればいいでしょう。

次にMayaaにこのInjectionResolverを登録しましょう。これは他の設定と同様で、src/META-INFなどの直下にorg.seasar.mayaa.provider.ServiceProviderというファイルを作成し、標準の設定ファイルから、次の部分を抜粋して書き換えます。

<provider>
    <templateBuilder
                class="org.seasar.mayaa.impl.builder.TemplateBuilderImpl">
        <resolver class="org.seasar.mayaa.impl.builder.injection.MetaValuesSetter"/>
        <resolver class="org.seasar.mayaa.impl.builder.injection.ReplaceSetter"/>
        <resolver class="org.seasar.mayaa.impl.builder.injection.RenderedSetter"/>
        <resolver class="org.seasar.mayaa.impl.builder.injection.InsertSetter"/>
        <resolver class="org.seasar.mayaa.impl.builder.injection.InjectAttributeInjectionResolver"/>
        <resolver class="org.seasar.mayaa.impl.builder.injection.EqualsIDInjectionResolver">
            <parameter name="reportUnresolvedID" value="true"/>
            <parameter name="reportDuplicatedID" value="true"/>
            <parameter name="addAttribute"
                                value="{http://www.w3.org/TR/html4}id"/>
            <parameter name="addAttribute"
                            value="{http://www.w3.org/1999/xhtml}id"/>
        </resolver>
        <!-- EID対応のため独自のものを加えている -->
        <resolver class="com.example.hogehoge.MyEqualsEIDInjectionResolver">
            <parameter name="reportUnresolvedID" value="true"/>
            <parameter name="reportDuplicatedID" value="true"/>
        </resolver>
        <resolver class="org.seasar.mayaa.impl.builder.injection.XPathMatchesInjectionResolver"/>
        <parameter name="outputTemplateWhitespace" value="true"/>
        <parameter name="outputMayaaWhitespace" value="false"/>
        <parameter name="optimize" value="true"/>
    </templateBuilder>
</provider>

この状態でWebアプリを立ち上げると、思った通りにe:idと記述した時、標準とは違う任意のファイル解決ルールでプロセッサーをひもづけることができました。しかし、まだ問題があります。今のままではmayaaファイルを変更・追加しても、Webアプリケーションを再起動するまで反映してくれません。これを対応するためには、SourceDescriptorの実装が必要ですが、長くなるので次回以降にしたいと思います。概要だけ説明すると、getTimestampをOverrideするだけです。