S2Strutsで元の画面に帰りましょう処理
前から余裕が出来たら書こう書こうと思ってたネタを。
前にSeasarコンで、id:kanagさんの隣にたまたまなったので、ちょこっと話した事なんですが。以下、POJO Action かつ無設定Struts前提で。
validatorでひっかかると
でも、validatorでひっかかった分だけでなくて、ロジックの中でハンドリングするエラーも同じように元の画面に戻してやりたい。ログイン失敗とか。S2Struts-exampleで云うと、AppRuntimeExceptionを継承した例外をキャッチした場合。
今やってるの、ログイン専用画面のほかにトップページのサイドバーにもログインフォームがあるんですよ。validatorでひっかかったのはトップページに戻るんだけど、ログイン失敗だと、色々と工夫しないと元の画面に戻れない。
Actionクラスをログイン専用画面とトップ画面のそれぞれ別に作る、とか。
で、要するに、input属性か
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()の内容を突っ込んじゃえばいいし。
但し、規約として、必ず
えー長くなりましたが、こんなんで上手くいきました。
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クラスがリクエストから取れます。