ログアウト||セッションタイムアウト時に処理したい
どっかにオーバーライドする場所あるかねえとかWebSessionとかのソースたどり始めて、ふと思う。
これで動くんじゃね?
public class TestSession extends WebSession implements HttpSessionBindingListener { private static final long serialVersionUID = 1L; protected TestSession(Request request){ super(request); } @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println("**** セッション生成 ****"); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("**** セッション破棄 ****"); } }
HttpSessionBindingListenerていうのは、あれです。セッションが生成/破棄されたとき通知してくれるやつ。
動きました。そりゃそうだセッションオブジェクトだもの。
追記:
これ注意が必要。具体的に言うと、セッションタイムアウトのタイミングだと、Session.getApplication()がExceptionを返します。
getApplication()はThreadLocalに格納されたアプリケーションオブジェクトを取得する仕組みだから、バックグラウンドプロセスからは自アプリケーションを取得できないんですね。どうしたものか。
AbstractChoiceでnullを選択肢としてつかう
まあ、そんな設計まずいかんだろという話はおいといて。
RadioChoice<Boolean> choices = new RadioChoice<Boolean>("wicket:id",Arrays.asList(null,true,false)); choices.setChoiceRenderer(new IChoiceRenderer<Boolean>(){ private static final long serialVersionUID = 1L; @Override public Object getDisplayValue(Boolean value) { if(value==null){ return "どちらともいえない日本人的回答"; } return value?"はい":"いいえ"; } @Override public String getIdValue(Boolean object, int index) { if(object==null){ return "-1";//AbstractSingleSelectChoice.NO_SELECTION_VALUE }else{ return object.toString(); } } }); //関係ないけど改行しない場合こんなかんじだよね choices.setSuffix(" ");
NO_SELECTION_VALUEがprotectedなので、ちょっといやんなかんじ。
まあ、どんな状況でも未選択を残したい場合とか、プロパティファイルの"nullValid"がアレな場合とかに。
Google Protocol Buffersしてみた
これ Developer Guide | Protocol Buffers | Google Developers。
まずは、GitHub - protocolbuffers/protobuf: Protocol Buffers - Google's data interchange formatからコンパイラとソースをダウンロード。
で、protoc.exeを「protobuf-2.0.0beta/src」にコピーして 「protobuf-2.0.0beta/java/README.txt」の記述どおりにMavenでごにょごにょっとjarを作る。
次にXMLでいうDTDみたいなものを作成。こんなかんじ。とりあえず、ファイル名は「rss_test.proto」としました。
package prototest; option java_package ="koyane.prototest"; option java_outer_classname = "RssTest"; message Item { required string title = 1; required string link = 2; optional string description =3; required string creator = 4; required string date = 5; optional string subject = 6; } message RssChannel { required string title = 1; required string link = 2; optional string description =3; repeated Item items =4; }
なんとなーくjavaとかC++ぽい表記。リファレンスよまなくても何だかわかりそう。雰囲気で。
「option java_package」は生成されるJavaクラスのパッケージ。指定されてなければ「package」の記述が適用されるらしい。
「option java_outer_classname」は生成されるJavaクラスの名称。指定されてなければファイル名がクラスの名前に使われるみたい。この例でいくと、「rss_test.proto」だから、「RssTest」みたいな。
message XXX{ ... }
の部分は生成されるJavaクラスのインナークラスに該当してました。
この例ではstringしか使ってないけどint32(int)とかint64(long)とかbool(boolean)とかあります。
required は必須フィールド
optional は必須じゃないフィールド
repeated はまあListになる感じ
試してないけどmessageを宣言する順序は関係ありそう。サンプル見る限り。
で、.protoファイルを作成したら、コンパイラ protoc.exe の出番
protoc --java_out=/ rss_test.proto
でとりあえず、ファイルシステムのルートにJavaソースコードが生成されます。この例だと、koyane.prototest.RssTest.java。
ふー。やっと準備完了。ここからが読み書きです。
まずはインスタンスを生成してファイルに保存
package koyane.prototest; import java.io.FileOutputStream; import java.util.Iterator; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import koyane.prototest.RssTest.Item; import koyane.prototest.RssTest.RssChannel; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class TestWrite { public static void main(String[] args) throws Exception{ Document doc = read("http://d.hatena.ne.jp/koyane/rss"); Element root = doc.getDocumentElement(); XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); xpath.setNamespaceContext(new NamespaceContext(){ @Override public String getNamespaceURI(String prefix) { if(prefix.equals("dc")){ return "http://purl.org/dc/elements/1.1/"; }else{ return XMLConstants.NULL_NS_URI; } } @Override public String getPrefix(String namespaceURI) { throw new UnsupportedOperationException(); } @Override public Iterator<?> getPrefixes(String namespaceURI) { throw new UnsupportedOperationException(); } }); Node cNode = (Node)xpath.evaluate("channel", root, XPathConstants.NODE); //ここ。ここがProtocol Buffers。 RssChannel.Builder channel = RssChannel.newBuilder(); channel.setTitle(xpath.evaluate("title/text()", cNode)); channel.setLink(xpath.evaluate("link/text()", cNode)); channel.setDescription(xpath.evaluate("description/text()", cNode)); NodeList iList = (NodeList)xpath.evaluate("item", root, XPathConstants.NODESET); for (int i=0;i<iList.getLength();i++) { Node iNode = iList.item(i); //あとここ。ここがProtocol Buffers。 Item.Builder item = Item.newBuilder(); item.setTitle(xpath.evaluate("title/text()", iNode)); item.setLink(xpath.evaluate("link/text()", iNode)); item.setDescription(xpath.evaluate("description/text()", iNode)); item.setCreator(xpath.evaluate("creator/text()", iNode)); item.setDate(xpath.evaluate("date/text()", iNode)); item.setSubject(xpath.evaluate("subject/text()", iNode)); channel.addItems(item.build()); } //で、ファイルに書き出し。 FileOutputStream output = new FileOutputStream("c:/rss_test_result"); channel.build().writeTo(output); output.close(); } private static Document read(String url) throws Exception{ HttpClient httpclient = new HttpClient(); GetMethod method = new GetMethod(url); httpclient.executeMethod(method); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(method.getResponseBodyAsStream()); method.releaseConnection(); return doc; } }
うりゃーーーっと、はてなのRSSを読み込んで保存。ひでーこーどだ。Protocol Buffersのところは超簡単。
次にこのファイルを復元。
package koyane.prototest; import java.io.FileInputStream; import koyane.prototest.RssTest.Item; import koyane.prototest.RssTest.RssChannel; public class TestRead { public static void main(String[] args) throws Exception{ RssChannel channel = RssChannel.parseFrom(new FileInputStream("c:/rss_test_result")); System.out.println("title : " + channel.getTitle()); System.out.println("link : " + channel.getLink()); System.out.println("description : " + channel.getDescription()); for (Item item : channel.getItemsList()) { System.out.println(); System.out.println("title : " + item.getTitle()); System.out.println("link : " + item.getLink()); System.out.println("description : " + item.getDescription()); System.out.println("creator : " + item.getCreator()); System.out.println("date : " + item.getDate()); System.out.println("subject : " + item.getSubject()); } } }
おお、できた。
今のところ私に使い道はないけど、面白いなあ。
ファイルダウンロード テキストファイルの場合
ついでにこっちも。矢野さんのサイトで細かく紹介しているから不要なエントリーともいう。
http://www.javelindev.jp/wicket/doc/tutorial02#i7
同じじゃ芸がないからちょっとちがう所をオーバーライド。まあやってることは同じなんだけれど。
StringBufferResourceStream stream = new StringBufferResourceStream("application/octet-stream"){ private static final long serialVersionUID = 1L; @Override public long length() { return asString().getBytes(getCharset()).length; } }; stream.setCharset(Charset.forName("Windows-31J")); stream.append(...);//文字列書き出しする IRequestTarget target = new ResourceStreamRequestTarget(stream,"text.xls"); getRequestCycle().setRequestTarget(target );
1.4m2でもStringBufferResourceStreamはマルチバイト対応はしていませんでした。
まあバグといえばバグなんだろうけど、JavaDocにマルチバイト対応してないよ、と記述しておけばいいレベルなのかなあと思わなくもない。
ファイルダウンロード OutputStreamに直接書き出したい場合
まあ調べればすぐわかるんだけれども、日本語の情報はなかったようなので。
業務で必要になった、POIで処理したExcelをダウンロードするボタン。
例外処理とかてけとー。
//請求書ダウンロードボタン Button<Void> downloadButton = new Button<Void>("downloadButton"){ private static final long serialVersionUID = 1L; @Override public void onSubmit() { IResourceStream stream = new AbstractResourceStreamWriter(){ private static final long serialVersionUID = 1L; @Override public void write(OutputStream output) { try { HSSFWorkbook workbook = //ここでExcel編集とかなんとか workbook.write(output);//適当に書き出す } catch (Exception e) { // TODO 例外処理 e.printStackTrace(); } } @Override public String getContentType() { return "application/vnd.ms-excel"; } }; //TODO ダウンロードファイル名 IRequestTarget target = new ResourceStreamRequestTarget(stream,"text.xls"); getRequestCycle().setRequestTarget(target); } };
お手軽。さすがWicket。
Integerを頭ゼロ埋めで表示するラベルを作る
public class CodeLabel extends Label<Integer> { private static final long serialVersionUID = 1L; private int zeroPadLength; public CodeLabel(String id, int zeroPadLength) { super(id); this.zeroPadLength = zeroPadLength; } @Override @SuppressWarnings("unchecked") public <T> IConverter<T> getConverter(Class<T> type) { if(type == Integer.class){ return new ZeroPaddingIntegerConverter(zeroPadLength); } return super.getConverter(type); } }
無名クラスでやってもいいけど、何度も出てきそうなので、クラスにしてみる。Generics使ったときのgetConverter()の書き方がいまいちわからんなあ。
追記:変だと思っていたらm2でここらかわってました。
public class CodeLabel extends Label<Integer> { private static final long serialVersionUID = 1L; private int zeroPadLength; public CodeLabel(String id, int zeroPadLength) { super(id); this.zeroPadLength = zeroPadLength; } @Override public IConverter<Integer> getConverter(Class<Integer> type) { return new ZeroPaddingIntegerConverter(zeroPadLength); } }
まあそうだよね。納得。
Formの継承クラスを作成せずにエラーメッセージを変える
結局つかわなかったのだけれど、調べたので。
Form<UserBean> userForm = new Form<UserBean>("userForm"){ private static final long serialVersionUID = 1L; @Override public String getValidatorKeyPrefix() { return "ユーザー"; } }; TextField<String> userId = new TextField<String>("id"); userId.setRequired(true); userId.add(new PatternValidator("^[0-9A-Za-z]*$")); userForm.add(userId); TextField<String> kana = new TextField<String>("kana"); kana.setRequired(true); kana.add(new PatternValidator("^[ア-ン]*$")); userForm.add(kana); ...
getValidatorKeyPrefixというメソッドをオーバーライドする。元はnullを返してるだけなので、単に好きな文字列返してみる。
プロパティファイルはこんな
ユーザーRequired="${label}"を入力しろやゴラ ユーザーid=ユーザーID ユーザーid.PatternValidator="${label}"は半角英数じゃゴラ ユーザーkana=ユーザ名フリガナ ユーザーkana.PatternValidator="${label}"は全角カタカナじゃゴラ
これでパッケージを除くFormクラス名の代わりにPrefixの文字列が使われる感じ。だいたい。
まあ厳密にいうとプロパティファイルのキーの指定ルールとか変にわかりにくいのだけれど、エラーメッセージを参照するとわかります。となげだす。