どっこい生きてる

1ヶ月以上音沙汰なしで御座居ましたが、仕事して子供寝かしつけてたらそればっかりの毎日になってました。

それでもタモリ倶楽部は見なきゃいけないんですけどね、もうね、行ですよ、「ぎょう」。タプルじゃない方の。

ようやっと先が見えた。心の余裕がでけた。

S2Strutsで元の画面に帰りましょう処理

前から余裕が出来たら書こう書こうと思ってたネタを。

前にSeasarコンで、id:kanagさんの隣にたまたまなったので、ちょこっと話した事なんですが。以下、POJO Action かつ無設定Struts前提で。

validatorでひっかかるとタグか、StrutsActionアノテーションのinput属性(まあstruts-config.xmlの、ですが)で指定したページへ戻る訳です。

でも、validatorでひっかかった分だけでなくて、ロジックの中でハンドリングするエラーも同じように元の画面に戻してやりたい。ログイン失敗とか。S2Struts-exampleで云うと、AppRuntimeExceptionを継承した例外をキャッチした場合。

今やってるの、ログイン専用画面のほかにトップページのサイドバーにもログインフォームがあるんですよ。validatorでひっかかったのはトップページに戻るんだけど、ログイン失敗だと、色々と工夫しないと元の画面に戻れない。

Actionクラスをログイン専用画面とトップ画面のそれぞれ別に作る、とか。

で、要するに、input属性かタグの値が判ればいいわけですよ。そっから値をとってそっちへforwardしてやればよろしい。

タグの場合は簡単。普通にリクエストパラメータを取得してやれば宜敷い。Base64でデコードしてやればよいのじゃ。

String param = request.getParameter(Constants.PAGE_NAME_ELEMENT_VALUE);
String inputUrl = new String(Base64Util.decode(param)); 

RequestDispatcher dispatcher = request.getRequestDispatcher(inputUrl);

try {
    dispatcher.forward(request,response);
} catch (ServletException e) {
    //あれやこれや
} catch (IOException e) {
    //あれやこれや
}

できたぞー。

これでまあいいかって思ったんですけど、一部入力確認画面とかでinput属性を指定してる所がありまして、やっぱりそっちも対応しないといけないでしょう、と。

どうやってinput属性って取るんだろうと思って、色々とソース追っかけたんですけど、ActionMappingクラスってのにgetInputメソッドなんてのがあるじゃないですか。これだー。

どうやってとろう・・・。こちとらPOJO Actionですから、うーむ。

で、S2RequestProcessorにThrowsInterceptorかまして、そいつでAppRuntimeExceptionをハンドリングしてやればいいじゃんって事に。S2RequestProcessorのprocessとかprocessActionFormとか、引数にActionMappingクラスがあるメソッドをpoint cutにすれば、MethodInvocationから引数取れますから、一件落着。

出来たー。と、ここでサンプルソースを出さないのには訳がある。

デバグしながらなんとなくrequestの中身を見てたら、居ましたよ、ActionMappingが。

ActionMapping am = (ActionMapping)request.getAttribute("org.apache.struts.action.mapping.instance");
String inputUrl = am.getInput();

とれたー!

てな訳で、先のThrowsInterceptorはActionクラスにかます事になりました。requestやresponseの取り方は、http://d.hatena.ne.jp/rokugen/20060610#1149866602のコメント欄の見ましょう。

ActionMapping am = (ActionMapping)request.getAttribute("org.apache.struts.action.mapping.instance");

String inputUrl = null;

if(am != null){
    inputUrl = am.getInput(); 
}

if(inputUrl == null || "".equals(inputUrl)){
    String param = request.getParameter(Constants.PAGE_NAME_ELEMENT_VALUE);
    inputUrl = new String(Base64Util.decode(param));
}

RequestDispatcher dispatcher = request.getRequestDispatcher(inputUrl);

try {
    dispatcher.forward(request,response);
} catch (ServletException e) {
//あれや
} catch (IOException e) {
//これや
}

取り敢えず全部のせで。本当のソースはちゃんとメソッドやクラスを分割してもうちょっと綺麗ですよ。

結局この処理、AppRuntimeExceptionが投げられた時以外にも任意のタイミングで呼び出したくなったので(キャンセルとか)、interceptorからは外に出したんですけどね(だからtry〜catchが書いてある)。

で、こうしてみたらですね、これはなかなかよかった。今まではActionクラスで、Service層でAppRuntimeExceptionが投げられた時のページ遷移を設定しなきゃいけないので、Service層の実装を覗きみないといけなかったんですが、interceptorにしたらService層でどんなAppRuntimeExceptionが投げられるのか、はたまた投げられないのか、意識しなくていいんですよ。なんにもcatchしないの。

public class LoginActionImpl implements LoginAction {
    private LoginService loginService;  
    private LoginDto loginDto;
    
    public String login() {
        loginService.login(loginDto);
        return SUCCESS;
    }
}

これでおしまい。ユーザーが存在しません、とかパスワードが間違ってます、とか、そういうのはService層でよろしくやってくれます。

エラー文言は、S2Struts-exampleにあった

errors.detail={0}

に、AppRuntimeException#getMessage()の内容を突っ込んじゃえばいいし。

但し、規約として、必ずタグかinput属性は書いておきましょう、ってのを守ってもらわないと白紙の画面が。

えー長くなりましたが、こんなんで上手くいきました。

Mao改めS2Mai

てな訳で仕事を言い訳になんも手をつけてなかったMaoですが、id:skimuraさんからお便りを戴きまして、なんと、S2Maiとしてもう実装しちゃったとの事で、なんと、私ついにコミッタになってしまいました。

やっと落ち着いたので心を入れ替えて頑張ります。何せ初めての事だもんでもう。

ギターを作った

2月にオーダーしたギターが先月、完成しました。

完全フルオーダー。無茶苦茶変形デザイン。ピックアップレイアウトなんか、前からテレキャス → P-90 → ハム、ですからもう、誰が使うんだって話ですよ。俺だけど。

ネック、トレモロユニット、ピックアップは持ち込み。木工、塗装、組み込みで、締めて19万円強。

オリジナルのギターブランドも持つ割と有名な工房に頼んだんですが、とても綺麗です。すばらしいです。頼んで良かった。もうギターは買わない。打ち止め。

で、やはり乾かしながら重ね塗りをする塗装に一番時間がかかったんですが、そう云う平行作業に当てられる時間を圧縮して、ビルダーの方に実際に俺ギターに占有された時間を聞いたんですよ。ざっくりでいいですから、と。

そしたら、うーん2ヶ月くらいかなぁ、だって。

1人月10万しないんですよ、10万!

で、品質最高ですよ。

もうね、負けましたね。いくら持ち込み部品が多かったとは云えですよ、カスタムメイドでこれは凄い。

メールで何度も入念に打ち合わせもしたし、要件定義もばっちりで、実際に見ないと判らない時はこっちの休みの日に合わせて工房を開けてくれたり、こっちが気付かなかった点なんかも気付かせてくれたり、製造業としてもサービス業としても素晴らしかったです。

ギターやベースをたしなむSEさんは、一度カスタムメイドで一本作ってみると、刺激になりますよ多分。

S2Strutsで元に帰るの続き

トラバを戴いたので(有難う御座居ます)、もうちょっと。Actionクラスにアスペクトかますinterceptorなんですが。

public class ActionThrowsInterceptor extends ThrowsInterceptor {
    private WebUtils webUtils; // 上に書いた戻る処理を外出ししたメソッドのあるクラス

    public void handleThrowable(AppRuntimeException ex, MethodInvocation invocation) 
        throws Throwable {
        
        MessageManager.addGlobalError("errors.detail",ex.getMessage());
        MessageManager.saveErrors();
        
        webUtils.fowardToInputPage();
        
    }
(以下略)

なんて感じです。

AppRuntimeExceptionを継承してれば、InvalidPasswordExceptionだろうが、UserNotExistExceptionだろうが(名前がださいとか突っ込みは聞かなかった事に致します)、catchして元画面のタグの場所に例外のメッセージを表示します。

で、MessageManagerをこんなところで使っちゃっていいんだろうか、と不安が。

ソースを見たら。

public class MessageManager {

(中略)
    public static void addGlobalError(String key, Object value0) {
        getMessageFacade().addGlobalError(key, value0);
    }
(延々と略)
    private static MessageFacade getMessageFacade() {
        return (MessageFacade) SingletonS2ContainerFactory.getContainer().getComponent(MessageFacade.class);
    }
}

ってなってまして、SingletonS2ContainerFactoryから都度コンテナを、そっからMessageFacadeを取得してるんで、大丈夫なんじゃないかと。ServiceLocatorのパターンと一緒って事で。

大丈夫、だよね・・・・。

誰かに突っ込まれる前に

あわわ、ActionMapping、定数があるよなあそりゃ。当たり前だよ。直しておいた。

ActionMapping am = (ActionMapping)request.getAttribute(Globals.MAPPING_KEY);

これでActionMappingクラスがリクエストから取れます。