ページテンプレート的な使い方
Wicketropyをいぢってます。基本的な部分が分かるまで、もう少しいじる予定。
今回はページテンプレート対応を行ってみました。Strutsで言うTilesのようなものですね。デザインの共通化を図る目的で使用されます。
Wicketの場合ですが、テンプレートとなるベースのページの、コンテンツを埋めたい部分に
ただ、これだけだと、
例えば以下のようなBasePage.html を用意します。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/" lang="ja" xml:lang="ja"> <body> <div id="wrapper"> <!-- Header start --> <div id="header"> <h1 id="title">Wicketropy Entry</h1> <!--<div id="description">サイトの概要またはサブタイトル</div>--> </div> <!-- Header end --> <div id="contents"> <!-- このwicket:childタグの内容が差し換わる --> <wicket:child> <h2 class="contents_title"><a href="#">コンテンツタイトル</a></h2> <div class="contents_body"> <p>ここにページのメインコンテンツを記述します。</p> <h3>小見出し</h3> <p>コンテンツ続き</p> </div> </wicket:child> </div> <!-- Main contents end --> <!--中略--> </div> </body> </html>
BasePage.java はWebPageのサブクラスです。特に変わったことはしていません。
で、上記のBasePage.htmlを利用したページ、EntryPage.htmlは以下のように作ります。埋め込む内容を書くだけの簡単なお仕事です。
<html> <body> <wicket:extend> <h2 class="contents_title"> <span wicket:id="title">コンテンツタイトル</span> </h2> <div class="contents_body"> <p wicket:id="body">ここにページのメインコンテンツを記述します。</p> <h3 wicket:id="timestamp">日付</h3> <p>ID = <span wicket:id="id">12345</span></p> <p><a wicket:id="permalink">permalink</a></p> <p><a wicket:id="editPage">このページを編集</a></p> <p><a wicket:id="newPage">新規ページの作成</a></p> <p><a wicket:id="randomPage">ランダムページ</a></p> </div> </wicket:extend> </body> </html>
そしてJava側でページテンプレートとコンテンツの対応付けです。EntryPage.java は、BasePage.htmlのページテンプレートを使用することを明記するため、BasePage.javaのサブクラスとします。
public class EntryPage extends BasePage { private static final Logger logger = LoggerFactory.getLogger(EntryPage.class); @Inject private Service service; public EntryPage(PageParameters parameters) { super(parameters); logger.info("parameters = " + parameters.toString()); //省略 } }
これでOK。コンパイル&開発モードで実行し、EntryPage.htmlにアクセスした結果は、以下のようになります。ちゃんとできてます!
<!--省略--> <wicket:child><wicket:extend> <!--EntryPage.htmlのコンテンツが表示される--> </wicket:extend></wicket:child> <!--省略-->
Wicketのいいところは、各HTMLページはブラウザでもそのまま見ることができる点ですね。wicket:id, wicket:child, wicket:extendは、(多分ですが)HTMLとは別の名前空間になっているため、ブラウザからも無視されるようになっているのでしょう。そこが素晴らしい。
ページテンプレート側に複数のwicket:childを埋め込むことができないんじゃないか、という疑問が湧いてきたのですが、これは多分Panelの自作で回避することができると思います。その辺はまた別途調査です。
最後に。Wicketropyのデザインは、 http://jp.cssez.com/ を使用させていただきました。ありがとうございます。
Wicketropyを少し直した。あとTODO。
前回気にしていた部分を直した。
- idという名前の変数のgetterをWebPageのサブクラスで作ることが出来ない。
- URLにはidを使い、ポストする際にはtropyIdという、同じ意味の別名を使ったことで混乱。
最初の方は、そもそもEditPage.javaのフィールド変数およびgetter/setterと、model/TropyEntry.javaのフィールド変数およびgetter/setterが重複していたので、最初からTropyEntryのインスタンスにフォームからの結果を格納させるように変更した。
public class EditPage extends WebPage { private TropyEntry entry; //(中略)... //CompoundPropertyModel model = new CompoundPropertyModel(this); CompoundPropertyModel model = new CompoundPropertyModel(entry);
2番目の方は、先にテストコードからtropyIdを消し、idに統一。テストコードを実行し、失敗することを確認。それからコードの修正に取りかかった。
ただ、ユニットテストの結果とjettyで動かした結果で異なる点があった。
public EditPage(PageParameters parameters) { super(parameters); logger.info("parameters = " + parameters.toString());
蒸気のコードの結果、ユニットテストの場合、
parameters = id = "1"
と表示されたのに対し、jettyで動かした場合、
parameters = id = ["1"]
となった。つまり、ユニットテストの場合はString, jettyで動かした場合はString[]が返ったわけ。理由はまだ調べていないが、サーブレットのrequest.getParameters()あたりの兼ね合いなんだろうか。。。
それはともかく、Wicketの動くサンプルは充実した方が世のためになるかと思うので、自分のヘタレなソースももう少し洗練させて、Wicketの機能もモリモリ使っていこうかなと考えています。直近のTODOはこんなところ。
- 入力の検証
- ページのテンプレート対応
- コンポーネント作成
恥を忍んでWicketropy (WicketでTropy)を晒してみた
Wicketの練習として、id:amachangが作っていたようなTropy実装を作ってみた。しかも、何を血迷ったか、CodeReposを利用してコミットまでしてしまった orz
http://coderepos.org/share/browser/lang/java/Wicketropy/trunk
いい感じに修正してくだせぇ…。
まず、コメントが全然ないのがダメダメです。
何故かかんかったかというと、コメントを書くよりもまずはWicketに慣れたかったため。まぁ簡単なソースだと思うので、多分コメントがなくても大丈夫だとは思うけど…心配です。
設計として考えたところと言えば…、
- 一応permalinkを持った。
- なるべくstatelessなコンポーネントばかりを使うよう心がけた。
- プレゼンテーション層はWicket, ビジネスロジック層+パーシステンス層は分けずPOJO、それらをGuiceで繋いだかたち。
こんなところでしょうか。
逆にハマッたのが、こんなところでしょうか。。。
- idという名前の変数のgetterをWebPageのサブクラスで作ることが出来ない。
- URLにはidを使い、ポストする際にはtropyIdという、同じ意味の別名を使ったことで混乱。
- WicketのユニットテストができるWicketTesterのコンストラクタ引数にWebApplicationのサブクラスのインスタンスを渡し忘れていて、GuiceのInjectionが有効にならなかった orz
こんなところでしょうか。小人さんの添削or修正を期待して、寝よう Zzz...
mapの並列処理化
こんなんができた。スレッドプールを使いまわす関係上、staticなメソッドじゃなくなったことが大きな違い。また、各要素に対してFunctionを並列処理で動かすためのCallableインスタンスを作るために1回コレクションを走査し、各要素の計算結果を受け取るためにもう1回コレクションを走査する関係上、計算が軽いコレクションに対しては、あんまり有効じゃないかもしれません。しくしく。
スレッドプールのスレッド数はCPUコア数+2。*2くらいでもよい気がする。
一部省略したものの、ソース。
/** * High-order functions for Java collections. */ public class CollectionUtil { private static CollectionUtil instance; private ExecutorService service; private CollectionUtil() { service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 2); } public static CollectionUtil getInstance() { if (instance == null) { synchronized (CollectionUtil.class) { if (instance == null) { instance = new CollectionUtil(); } } } return instance; } public static <T0, T1> List<T1> map(Collection<T0> collection, Function<T0, T1> function) { List<T1> result = new ArrayList<T1>(collection.size()); for (T0 element : collection) { result.add(function.apply(element)); } return result; } public <T0, T1> List<T1> map2(Collection<T0> collection, Function<T0, T1> function) throws InterruptedException, ExecutionException { List<Future<T1>> futureList = new ArrayList<Future<T1>>(collection.size()); //各要素を、スレッドプールに計算させる。 //ElementRunnerがCallableインタフェースの実装クラスなので、 //service内のスレッドプールが計算してくれます。 for (T0 element : collection) { futureList.add(service.submit(new ElementRunner(element, function))); } //各要素の計算結果を取得する。 List<T1> result = new ArrayList<T1>(collection.size()); for(Future<T1> element : futureList) { result.add(element.get()); } return result; } } class ElementRunner<T0, T1> implements Callable<T1> { private T0 t; private Function<T0, T1> fn; public ElementRunner(T0 t, Function<T0, T1> fn) { this.t = t; this.fn = fn; } public T1 call() throws Exception { return fn.apply(t); } }
ツッコミ歓迎。
mapの並列処理化 (まだ出来ていない)
先日実装したmapについて、各要素に対してマルチスレッドで処理させると、素敵かもしれない。
君のプログラミング言語で、これ、できる? - The Joel on Software Translation Project に書かれているGoogleのMapReduceみたいなイメージですね。
Schemeの場合、mapの場合、リストの各要素の処理順序は規定されていないため、各要素に対してどんな順序で処理しても構わない。上記リンクの通り、リストを半分にして、それぞれを並列処理させることだって可能だし、各要素に対してワーカスレッドに処理させる、なんてことも可能。
さてJavaに戻ってきた場合、ExecutorフレームワークのThreadPoolExecutor(ExecutorServiceインタフェースを実装)が、ワーカスレッドのスレッドプールとして非常に使いやすい。特に、invokeAll()メソッドを使えば、上記の処理をあっさりやってもらうことができる。
(参考URL: http://ja.doukaku.org/comment/5012/)
問題は、ExecutorService.invokeAll()メソッドはCallableインタフェースの実装クラスを要素に持つCollectionしか受け付けないところ。一方、前のFunctionインタフェースは上記を満たしていない…さてさて、どうしようかなぁ。考え中。
更に話は脱線するのですが、Javaのように強い型付けの言語の場合、IDEによる補完なんぞが非常に強力なので、実装する側は初歩的なエラーを事前に取る事が出来て便利ですね。
一方、フレームワークやライブラリなど、仕組みを作る側としては…難しい。どんなインタフェースを受け付けるのか、返り値はどんな型が望ましいのか。インタフェースは自作で定義すべきか、他のを借りるべきか云々。仕組みを作る側は、考えなければならないことは多いですね。
当然型付けの弱い言語だってその辺を考える必要がありますが、型付けが弱い、柔軟な言語なので、産みの苦しみは少し軽いんじゃないかなぁと考えています。実際どうでしょう??
…というわけでTODO。うまいことMapReduceのようなmapの実装を考える。
Genericsを使うJava用のmap, filter, foldなど。
いちいちfor文を書くのに疲れてきたので、書いてみた。
ノーコメントですまそ。
map, filter, foldを実装。
/** * High-order functions for Java collections. */ public class CollectionUtil { public static <T0, T1> List<T1> map(Collection<T0> collection, Function<T0, T1> each) { List<T1> result = new ArrayList<T1>(collection.size()); for (T0 element : collection) { result.add(each.apply(element)); } return result; } public static <T0> List<T0> filter(Collection<T0> collection, Function<T0, Boolean> each) { List<T0> result = new ArrayList<T0>(); for (T0 element : collection) { if (each.apply(element)) result.add(element); } return result; } public static <T0, T1> T1 foldLeft(Collection<T0> collection, Procedure<T0, T1> each) { for (T0 element : collection) { each.apply(element); } return each.get(); } }
各要素に対して適用する関数。
/** * Function that returns value of type B using value of type A. */ public interface Function<A, B> { public B apply(A element); }
各要素に対して適用し、その結果を保持することが可能な手続き。
/** * Foldable has a function and a state (like a closure). */ public interface Procedure<A, B> extends Function<A, B> { public B get(); }
で、これができると、こういうことができるようになります。例えば1から10の整数に対する総和を返す場合。でも各計算結果を保持するためには1個名前付きのクラスを用意しなければ状態の保持しどころがないので難しい…。
1つ、参照渡しのようなイメージの引数を渡して、そこに状態を保持させる作戦とかどうだろう。それならできるかな?
class Sum implements Procedure<Integer, Integer> { private Integer value; Sum (Integer initial) { this.value = initial; } public Integer apply(Integer element) { value += element.intValue(); return value; } public Integer get() { return Integer.valueOf(value); } } @Test public void testFold() { List<Integer> oneToTen = Arrays.<Integer>asList(1,2,3,4,5,6,7,8,9,10); assertEquals(Integer.valueOf(55), CollectionUtil.foldLeft(oneToTen, new Sum(0))); }
mapやfilterなんかは簡単なんだけど、各要素の計算結果を保持するライブラリとして持っておきたいな。もっと考慮すべき点はあるかな。
アノテーションを用いて既存クラスの活躍場所を増やす
アノテーションの勉強をしました。とは言え勉強の範囲は非常に狭く、実行時にアノテーションを解釈して、実行時に振る舞いを変えるにはどうすればいいか?というレベルです。Javaコンパイラが使用するアノテーション(@Overrideなど)は対象外です。
アノテーションについては、以下の記事が参考になります。
http://journal.mycom.co.jp/column/java/026/index.html
アノテーションを用いることで、ある既存クラスにアノテーションを付与することで、既存クラスに別の仕事をさせることが容易になりました。あまりrailsを知らないけれど、railsのact_as_*に近いのかな?
例えば、あるクラスが保持するデータを何らかの検索システムで検索したい、というような要望があったとします。
その場合、ぱっと思いつくのは以下のようなアノテーションがあればいいのかなと。
まずは最初の「検索可能である」というアノテーションを作ります。
@Retention(RetentionPolicy.RUNTIME) //実行時に使用可能 @Target(ElementType.TYPE) //クラスに対する注釈 public @interface Searchable { }
次に、「何の値で検索するか」のアノテーションを作ります。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) //メソッドに対する注釈 public @interface SearchField { String option() default ""; //option引数。省略時は空文字。 }
最後に、上記アノテーションを処理するクラスを作ります。
class SearchEngine { public static SearchResult search(Object searchable) throws Exception { //Searchableアノテーションのないクラスは検索不可 if (!searchable.getClass().isAnnotationPresent(Searchable.class)) { //例外の送出 } try { //各メソッドが持つアノテーションを調べ、検索関係のアノテーションを発見したら //検索条件を作成する List<SearchCondition> searchConditions = new LinkedList<SearchCondition>(); for (Method method : searchable.getClass().getMethods()) { if (method.isAnnotationPresent(SearchField.class)) { //SearchFieldアノテーションが付いたメソッドを呼んで検索条件を取得する String query = (String) method.invoke(searchable, new Object[0]); //SearchFieldアノテーションのoption引数の値を取得する String option = method.getAnnotation(SearchField.class).option(); SearchCondition cond = new SearchCondition(); cond.setQuery(query); cond.setOption(option); searchConditions.add(cond); } } //あとは検索処理をこの辺に書く return ..... } catch (Exception e) { e.printStackTrace(); throw e; } } }
こんなのを作れば、例えばProductの製品名で検索を要求したい場合、以下のようにアノテーションを付与し、SearchEngine.search() に渡せばOK。この場合、Product.getName()の値で、ignoreCaseオプションつきで検索する、というイメージです。
@Searchable public class Product { @SearchField(option="ignoreCase") public String getName() { //... } }
アノテーションは実行時に解釈する処理用のアノテーションであれば、実装は思ったほど難しくありません。コンパイラだけが使用するアノテーションなどは難しいのかもしれませんが、実行時用のアノテーションは使いどころが色々ありそうなので、勉強してみる価値はありそうです。
今はXMLをXPath指定でクラスにマッピングさせるようなライブラリが欲しいのですが、なかなか難しく、ちょっと中断気味 orz