久々にELResolver
前のエントリ見て遅延評価式書こうと思ったけど、面倒なのでやめ。
というわけで久々にELResolverをいじるだけにしておく。
やることはエスケープ(面倒なので今回はかぎかっこつけただけ)やボールド、JSONデータをつけること。JSPのところと実行結果を見てもらったほうが早い。
ELResolverを作成。前のエントリのサンプルを元にカスタマイズ。
public class CustomELResolver extends ELResolver { public Object getValue(ELContext elContext, Object base, Object property) { if (property == null) { throw new PropertyNotFoundException(); } if (base == null && property.toString().equals("newClass")) { System.out.println("-----------------------------------------"); System.out.println("オブジェクトを取得"); System.out.println(base + "/" + property); elContext.setPropertyResolved(true); return new NewClass(); } else if (property.toString().toLowerCase().equals("json")) { System.out.println("JSON"); System.out.println(base + "/" + property); elContext.setPropertyResolved(true); return JSON.encode(base); } else if (base != null && base instanceof NewClass) { System.out.println("オブジェクトからmessageプロパティ取得"); System.out.println(base + "/" + property); elContext.setPropertyResolved(true); return ((NewClass) base).getMessage(); } else if (base != null && base instanceof String) { elContext.setPropertyResolved(true); if (property.toString().toLowerCase().equals("escape")) { System.out.println("escape"); System.out.println(base + "/" + property); return "「" + base + "」"; } if (property.toString().toLowerCase().equals("bold")) { System.out.println("BOLD"); System.out.println(base + "/" + property); return "<b>" + base + "</b>"; } } System.out.println("その他"); System.out.println(base + "/" + property); return null; } public Class<?> getType(ELContext elContext, Object base, Object property) { System.out.println("getType"); return Object.class; } public void setValue(ELContext elContext, Object base, Object property, Object value) { // this scope isn't writable in the strict sense, so do nothing. } public boolean isReadOnly(ELContext elContext, Object base, Object property) { System.out.println("isReadOnly"); return true; } public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext, Object base) { return Collections.<FeatureDescriptor>emptyList().iterator(); } public Class<?> getCommonPropertyType(ELContext elContext, Object base) { if (base != null) { return null; } System.out.println("getCommonPropertyType"); return String.class; } }
適当なBean。クラス名もウィザードのデフォルトを使うくらいの適当さが現れてる。
public class NewClass { private String message = "はろー"; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP Page</title> </head> <body> <h1>Hello World!</h1> ${newClass}<br> ${newClass.message}<br> ${newClass.message.escape}<br> ${newClass.message.bold}<br> ${newClass.message.escape.bold}<br> ${newClass.json}<br> </body> </html>
実行!
まぁうまくいったね。
JSONをHTMLにつけておくってのはscript全開なコードだとままあることなので、こういう用途もありかもと思いつけてみた。JSON変換はおなじみのJSONIC 1.1。
ELファンクションとかたるいのでおいらはこういうのが好きかな。Stringを返すプロパティはデフォでエスケープしたいと思ったけれども、ELResolverは前から順次個別に評価するのでそのままだと普通は出来ない。
個人的にはELファンクションでエスケープってのは2年以上前にやりまくったけど意外と後から見ると非常にわかりにくくて今ではわりと封印している。
ある意味c:outのように前から判断させるのはありかもしれない。かといって
${out.newClass.message}
とかくのは馬鹿らしい。
${newClass.message}
との区別もつきにくいし、エスケープされているかを見逃してしまいそう。
ここはやはりStringのデフォルトをエスケープにするのがよいかな。終端がオブジェクトで終わるとtoString()が使われるけど、これが使われることってあんまり無いと思うから。そしてエスケープしたくないときにだけ以下のように明示する。
${noescape.newClass.message}
これで、意外とすっきりしたと思う。やっぱりテンプレート系はデフォをエスケープ済みにしておいたほうがいいよねぇ?
こういうELResolverを前面に出したView特化フレームワークを作るのも面白いかもしれない。