Ruby on Railsに、flashというデータ入れる場所がある。
flash[:name] = "ほげたろう" # 格納時
name = flash[:name] # 取り出す時
こんな雰囲気で使える代物になってる。
このflashに入れたデータは、次のリクエストまで有効になっている。
なので、sessionみたいに一度入れたら消すまで消えないっていうものよりはお手軽になってる。
sessionにどんどこデータを入れまくって消す処理が無くて最後にはサーバを圧迫して悲しい末路を辿ることが少なくなってる素晴らしい?仕組みです。
因みに、次のリクエストが終わったら絶対消えるのかと言うとそうでもない。keepというメソッドを呼び出すことで、その次のリクエストまで持ち越すことが可能になってる。
flash.keep(:name) # 次のリクエストまで持ち越しておくれ
次のリクエストまで持ち越して欲しくない場合は明示的にdiscardというメソッドを呼ぶと次のリクエストでは消したりもできる。
Visual Web JSFで開発するときもコレホシイ!!ということで似たようなのを作ってみた。
まず、Webに依存しない部分でデータを保持する仕組みとデータのライフサイクルを管理するクラスを作る。
package com.wankuma.kazuki.vwjsf.flash;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* flashみたいなデータ保持をするクラス。
* refleshをリクエストの最後に呼ぶ使い方を想定してる。
* @author Kazuki
*/
public class FlashData implements Serializable {
// 前のリクエストで設定された値をとっておくMap
private Map<String, Object> prevCache = createMap();
// 現在のリクエストで設定された値をとっておくMap
private Map<String, Object> currentCache = createMap();
/**
* nameをキーにしてvalueを登録する。
* @param name
* @param value
*/
public synchronized void put(String name, Object value) {
currentCache.put(name, value);
}
/**
* nameで指定した値を取得する。
* 存在しない場合は、nullを返す。
* @param name
* @return
*/
public synchronized Object get(String name) {
if (currentCache.containsKey(name)) {
return currentCache.get(name);
}
return prevCache.get(name);
}
/**
* nameで指定した値を次のリクエストでも使えるようにする。
* @param name
*/
public synchronized void keep(String name) {
put(name, get(name));
}
/**
* nameで指定した値を、次のリクエストで消えるようにする。
* @param name
*/
public synchronized void discard(String name) {
Object value = currentCache.get(name);
if (value != null) {
currentCache.remove(name);
prevCache.put(name, value);
}
}
/**
* 前のリクエストのデータを消す。
*/
synchronized void reflesh() {
prevCache.clear();
prevCache = currentCache;
currentCache = createMap();
}
/**
* SynchronizedなMapを生成する。
* @return
*/
Map<String, Object> createMap() {
return Collections.synchronizedMap(new HashMap<String, Object>());
}
}
若干synchronizedしすぎかな?と思わないでも無いけど、とりあえずこんなもんで。
putとgetでデータの追加と取得が出来るようになっている。
内部では、前のリクエストのときに追加されたデータを保持するprevCacheと、現在のリクエストで追加されたデータを保持するcurrentCacheという2つのマップを持っている。
keepでは、データを取得して再度セットすることで、現在のリクエストで追加されたデータという扱いに強制的に持っていくことで、次のリクエストでも有効になるようにしている。
逆にdiscardでは、前のリクエストで追加されたデータの領域に無理やりデータを移動させている。
これで、次のリクエストではサヨウナラという感じになってる。
refleshというメソッドで、前のリクエストのデータを消している。
次にWebに依存する部分。Sessionに上で作ったFlashDataを保持するようにしている。
package com.wankuma.kazuki.vwjsf.flash;
import java.util.Map;
import javax.faces.context.FacesContext;
/**
* セッションでFlashDataを保持する人。
* @author Kazuki
*/
public class Flash {
// セッション上にFlashDataを保持するときに使うKey
private static final String SESSION_KEY = Flash.class.getName() + "_SESSION_KEY";
// インスタンス化禁止
private Flash() {}
/**
* セッション上からFlashDataを取得する。(無い場合は作る)
* @return
*/
synchronized static FlashData getFlashData() {
Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
FlashData flashData = (FlashData) sessionMap.get(SESSION_KEY);
if (flashData == null) {
flashData = new FlashData();
sessionMap.put(SESSION_KEY, flashData);
}
return flashData;
}
/**
* FlashDataへデータを設定する
* @param name
* @param value
*/
public static void put(String name, Object value) {
getFlashData().put(name, value);
}
/**
* FlashDataからデータを型指定で取得する。
* @param <T>
* @param name
* @param clazz
* @return
*/
public static <T> T get(String name, Class<T> clazz) {
return (T) get(name);
}
/**
* FlashDataからデータを取得する
* @param key
* @return
*/
public static Object get(String name) {
return getFlashData().get(name);
}
/**
* nameで指定した値を次のリクエストに持ち越すように指定する。
* @param name
*/
public static void keep(String name) {
getFlashData().keep(name);
}
/**
* nameで指定した値を次のリクエストで破棄するように指定する。
* @param name
*/
public static void discard(String name) {
getFlashData().discard(name);
}
}
そして、リクエストの最後にrefleshを呼ぶための人を準備する。PhaseListenerのRENDER_RESPONSEのafterのタイミングでrefleshを呼ぶようにした。
package com.wankuma.kazuki.vwjsf.flash;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
/**
* flashのデータをリクエストの最後でリフレッシュする。
* @author Kazuki
*/
public class FlashContextListener implements PhaseListener {
public void afterPhase(PhaseEvent event) {
// RENDER_RESPONSEの後(最後の最後)で、リフレッシュする。
Flash.getFlashData().reflesh();
}
public void beforePhase(PhaseEvent event) {
}
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
}
最後に、このPhaseListenerを登録するためにMETA-INFにfaces-config.xmlを作る。
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<lifecycle>
<phase-listener>com.wankuma.kazuki.vwjsf.flash.FlashContextListener</phase-listener>
</lifecycle>
</faces-config>
これをjarに固めて、Visual Web JSFのプロジェクト(まぁVisualWebである必要は無い)のライブラリに追加する。
テスト用にFlashTestという名前でVisual Web JSFのプロジェクトを作る。いつも通りPage1やApplicationBean1やSessionBean1やRequestBean1を削除する。
そして、上で作ったjarをプロジェクトに追加する。
んで新規にPageを2つ追加する。名前は何も考えずにPage1とPage2にした。Page1をスタートページにして、下のように画面を作る。
ページナビゲーションは下のような感じ。
Page1のTextFieldのtextプロパティをPage1のinputMessageプロパティにバインドする。
そしてbutton1のactionで下のようにFlashにデータを入れてPage2に遷移する。
public String button1_action() {
// Flashにデータを入れてPage2へ
Flash.put("Page1.inputMessage", inputMessage);
return "page2";
}
Page2にもinputMessageというプロパティを用意して、上から二番目のStaticText(abcと表示されてるやつ)のtextプロパティとバインドしておく。
そして、Page2のinitメソッドでFlashからinputMessageプロパティに値を代入するようにした。
// FlashからPage1で設定したデータを取得する
inputMessage = Flash.get("Page1.inputMessage", String.class);
3つあるボタンでは、上から何もしないボタン、Flashにkeepを指示するボタン、Page1に戻るボタンになっている。
public String button1_action() {
// do nothing
return null;
}
public String button2_action() {
Flash.keep("Page1.inputMessage");
return null;
}
public String button3_action() {
// Page1へ戻る
return "page1";
}
これで、Page1で入力したテキストが、Page2に表示されるようになっているはず。
Flashは、入力した次のリクエストまで有効なのでPage2の何もしないボタンを2回押すとテキストが消えるはず。
Flash.keepするボタンだと何度押してもテキストが表示され続けるはず!!ということで実行!
何もしないボタンを押した場合の実行結果
→→→
予想通り、何もしないボタンを2回押すと入力したテキストが消えた。
Keepボタンを押した場合の実行結果
→→→
こちらも予想通り、何度ボタンを押しても消えない。
いい感じにできてると思うけどどうだろうか?