T2+GAE+guice+AIR 多分その1

シルバーウィーク中に作るといっときながら様々な事にはまり、
まだまだ完成はできていないタイピングサイトです。

中途半端な状態ですが、とりあえず途中経過メモ

T2にPluginを設定してみる。

まず前回のエントリーまでの設定を行って、Pluginを設定してみます。

package jp.stk.gae.plugin;

import java.lang.reflect.Field;

import org.t2framework.commons.meta.MethodDesc;
import org.t2framework.commons.util.Logger;
import org.t2framework.t2.action.ActionContext;
import org.t2framework.t2.contexts.Request;
import org.t2framework.t2.plugin.AbstractPlugin;
import org.t2framework.t2.spi.Navigation;

public class PagePlugin extends AbstractPlugin {
    @SuppressWarnings("unused")
    private final static Logger logger = Logger.getLogger(PagePlugin.class);

	@Override
	public Navigation beforeActionInvoke(ActionContext actionContext,
			MethodDesc targetMethod, Object page, Object[] args) {
		return super.beforeActionInvoke(actionContext, targetMethod, page, args);
	}

	@Override
	public Navigation afterActionInvoke(ActionContext actionContext,
			MethodDesc targetMethod, Object page, Object[] args) {
		Class<?> t = targetMethod.getDeclaringClass();
		Request request = actionContext.getRequest();
		for (Field f : t.getFields()) {
			try {
				request.setAttribute(f.getName(), f.get(page));
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			}
		}
		return super.afterActionInvoke(actionContext, targetMethod, page, args);
	}

}

sastrutsのactionのパブリックフィールドがそのままリクエストに乗るっていうのが結構好きだったので、それをやってみました。
※本当は通信の方法によって分けたかったのですが、よくわかりませんでした。とりあえず、Pluginのお試しということで、たぶんすぐ消すかもです。

でこれをT2に読んでもらうために、guiceのモジュールに追加します。

package jp.stk.gae.module;


import jp.stk.gae.plugin.PagePlugin;
import jp.stk.gae.service.FuriganaService;
import jp.stk.gae.service.QuestionService;
import jp.stk.gae.service.impl.FuriganaServiceImpl;
import jp.stk.gae.service.impl.QuestionServiceImpl;

import org.t2framework.t2.plugin.Plugin;

import com.google.inject.AbstractModule;
import com.google.inject.servlet.ServletModule;

public class ApplicationModule extends AbstractModule {

	@Override
	protected void configure() {

		install(new ServletModule());
		bind(Plugin.class).toInstance(new PagePlugin());
	}
}

で動かせば、Pageクラスのパブリックフィールドが、Pageクラスの処理後に、リクエストにsetAttributeされ、Jspで呼び出せるようになります。
※あまりGuiceの仕様書は読んでないのでDI設計的にbindのやり方が、あってるか微妙です。

T2でAMF通信をしてみる。

タイピングアプリの問題取得をAIRを利用して、AMFによっておこなうようにします。
ちなみにAIRの実装はAmaterasのAIR GEARとFlashDevelopです。
FlexBuilderを買うお金はありません。。。(T_T)

ちなみにこれが最もハマりました。
そもそもはじめてのAMFかつほぼ未経験のActionScriptというのも原因なのですが、
泣きそうでした。

でとりあえず、まずJAVAソースです。
まず、AMFの通信を行う、Pageクラスのメソッドです。

	@SuppressWarnings("unchecked")
	@Amf
	public Navigation putQuestionList(final RequestDto requestDto) {
		List<Question> questionList = questionService.findAll();  //1
		if (questionList != null) {
			List<QuestionDto> questionDtoList = new ArrayList<QuestionDto>();
			for (Question question : questionList) {
				QuestionDto questionDto = new QuestionDto(); //2
				questionDto.setExplain(question.getExplain());
				questionDto.setExplainAlpha(question.getExplainAlpha());
				questionDto.setTitle(question.getTitle());
				questionDto.setTitleAlpha(question.getTitleAlpha());
				questionDtoList.add(questionDto);
			}
			ResponseDto responseDto = new ResponseDto(); //3
			responseDto.setQuestionDtoList(questionDtoList);
			return AmfResponse.to(responseDto);//5
		}
		ResponseDto responseDto = new ResponseDto(); //6
		responseDto.setQuestionDtoList(new ArrayList<QuestionDto>());
		return AmfResponse.to(responseDto);
	}
  1. BigTableから問題の全件を取得して、
  2. 問題用のDTOに詰め替えて、
  3. AMF返却用のDTOにぶちこんで
  4. AmfResponse.to(responseDto)でNavigationを返します。
  5. ここは気にしないでください。直す予定です。

ちなみにRequestDtoはこれからいろいろするためにもらってます。

で、BigTableから取得するとことか、このQuestionServiceはGuiceからDIされてますとか、全件返しちゃまずくないとか、6のとこ何よとかは置いといて、AMF返却用のQuestionDtoのソースです。

package jp.stk.gae.dto.question;


public class QuestionDto {
	/** title */
	private String title;

	/** title(ローマ字) */
	private String titleAlpha;

	/** 説明 */
	private String explain;

	/** 説明(ローマ字) */
	private String explainAlpha;

	/**
	 * titleを取得します。
	 * @return title
	 */
	public String getTitle() {
	    return title;
	}

	/**
	 * titleを設定します。
	 * @param title title
	 */
	public void setTitle(String title) {
	    this.title = title;
	}

	/**
	 * title(ローマ字)を取得します。
	 * @return title(ローマ字)
	 */
	public String getTitleAlpha() {
	    return titleAlpha;
	}

	/**
	 * title(ローマ字)を設定します。
	 * @param titleAlpha title(ローマ字)
	 */
	public void setTitleAlpha(String titleAlpha) {
	    this.titleAlpha = titleAlpha;
	}

	/**
	 * 説明を取得します。
	 * @return 説明
	 */
	public String getExplain() {
	    return explain;
	}

	/**
	 * 説明を設定します。
	 * @param explain 説明
	 */
	public void setExplain(String explain) {
	    this.explain = explain;
	}

	/**
	 * 説明(ローマ字)を取得します。
	 * @return 説明(ローマ字)
	 */
	public String getExplainAlpha() {
	    return explainAlpha;
	}

	/**
	 * 説明(ローマ字)を設定します。
	 * @param explainAlpha 説明(ローマ字)
	 */
	public void setExplainAlpha(String explainAlpha) {
	    this.explainAlpha = explainAlpha;
	}
}

ローマ字がAlphaって何よって思うかもですが、ローマ字を英語にするとなんていうのか知りません。
で次にAMF返却用のDTOです。

package jp.stk.gae.dto.question;

import java.util.List;

public class ResponseDto {
	/** 設問リスト */
	private List<QuestionDto> questionDtoList;

	/**
	 * 設問リストを取得します。
	 * @return 設問リスト
	 */
	public List<QuestionDto> getQuestionDtoList() {
	    return questionDtoList;
	}

	/**
	 * 設問リストを設定します。
	 * @param questionDtoList 設問リスト
	 */
	public void setQuestionDtoList(List<QuestionDto> questionDtoList) {
	    this.questionDtoList = questionDtoList;
	}
}

で最後に、GAEではBlazeDSがそのままだとうまいこと機能しないらしいので、T2AMFを利用して、通信をするための設定を行います。
web.xmlのT2Filterにパラメータを追加します。

	<filter>
		<filter-name>t2</filter-name>
		<filter-class>org.t2framework.t2.filter.T2Filter</filter-class>
		<init-param>
			<param-name>t2.rootpackage</param-name>
			<param-value>jp.stk.gae.page</param-value>
		</init-param>
 		<init-param>
			<param-name>t2.container.adapter</param-name>
			<param-value>org.t2framework.t2.adapter.GuiceAdapter</param-value>
		</init-param>
 		<init-param>
			<param-name>t2.exclude-resources</param-name>
			<param-value>css, js, png, gif, jpg</param-value>
		</init-param>
		<init-param>
                        <!-- これを入れる -->
			<param-name>t2.amf</param-name>
			<param-value>t2amf</param-value>
		</init-param>
	</filter>

ちなみにT2はMavenで取得すると、BlazeDsがもれなくついてきます。(Dependencyをもってるので。。。)なので、t2amfを使うなら消してもOKだと思われます。

つぎにactionScript側のソースです。
まず通信部分。

		private function setQuestionList():void{
			if(ro == null){
				ro = new RemoteObject("通信先");
				var endPoint:String = URLUtil.getFullURL("通信先URL" ,"t2.amf");
				var channel:Channel = URLUtil.isHttpsURL(endPoint)?
				new SecureAMFChannel(null,endPoint):new AMFChannel(null,endPoint);
				var channelSet:ChannelSet = new ChannelSet();
				channelSet.addChannel(channel);
				ro.channelSet = channelSet;
			}
			var token:AsyncToken = ro.putQuestionList(new RequestDto());
			token.addResponder(new AsyncResponder(handleResult,handleFault));
			questionList = new ArrayCollection();
		}

		public function handleResult(e:ResultEvent,token:Object=null):void{
			var responseDto:ResponseDto = ResponseDto(e.result);
			questionList = new ArrayCollection(responseDto.questionDtoList);//※
			gameInfo.status = GameInfo.STATUS_ALREADY;
		}

		public function handleFault(e:FaultEvent,token:Object=null):void{
			trace(ObjectUtil.toString(e));
		}

ソースはt2-haikuを参考にしました。

で次に、AMF受け取り用のQuestionDtoです。
※が気になった方もいるかも知れませんが、下で書きかます

/**
 * @author Administrator
 */
package jp.stk.gae.dto.question {
	[RemoteClass(alias="jp.stk.gae.dto.question.QuestionDto")]
	[Bindable]
	public class QuestionDto {
		private var _title:String;
		public function get title():String{return this._title}
		public function set title(title:String):void{this._title = title;}
		private var _titleAlpha:String;
		public function get titleAlpha():String{return this._titleAlpha}
		public function set titleAlpha(titleAlpa:String):void{this._titleAlpha = titleAlpa;}
		private var _explain:String;
		public function get explain():String{return this._explain;}
		public function set explain(explain:String):void {this._explain = explain;}
		private var _explainAlpha:String;
		public function get explainAlpha():String{return this._explainAlpha;}
		public function set explainAlpha(explainAlpha:String):void {this._explainAlpha = explainAlpha;}
		public function QuestionDto() {
		}
	}

}

次に、AMF受け取り用のResponseDtoです。

package jp.stk.gae.dto.question {
	[RemoteClass(alias="jp.stk.gae.dto.question.ResponseDto")]
	public class ResponseDto {
		public var questionDtoList:Array;//※
		public function ResponseDto() {

		}
	}

}

上の※もふくめて
BlazeDSを使ったことがある方は「あれ?」と思ったかもしれません。
ここがもっともはまったところです。

ResponseDtoにてJava側ではjava.util.Listで宣言しているのに、ActionScript側では、Arrayで宣言しています。普通(BlazeDS)はJavaでListを宣言した場合、ActionScript側はArrayCollectionかArrayListなのですが、t2Amfでは、ListはArrayとして返すようです。
このあたりはどこのサイトにも乗ってなく、
しぬほどハマりました。
(ちなみにBlazeDSをT2で利用する場合はArrayCollectionで返ります)
これでAMF通信ができました。

T2でAjax

今度はT2でAjaxをやってみます。
何をやるかというと、タイピングソフトなので、問題を作成するとき、日本語と、ローマ字を入力しなければならないのですが、その作業がめちゃくちゃめんどくさいのです。
なので、ボタンを押したらハイローマ字みたいな処理がほしかったのでそれをAjaxで作成します。

まず、Java側Pageクラスです。

	@Ajax
	@POST
	public Navigation tranceFormLatin(final WebContext context,
			@RequestParam("text")
			final String text) {
		String full = Transliterator.getInstance("Halfwidth-Fullwidth")
				.transliterate(furiganaService.getFurigana(text));
		String hira = Transliterator.getInstance("Katakana-Hiragana")
				.transliterate(full);
		String latin = Transliterator.getInstance("Hiragana-Latin")
				.transliterate(hira);
		String result = Transliterator.getInstance("Fullwidth-Halfwidth")
				.transliterate(
						latin
						.replace(" ", " ")
						.replace("、", ",")
						.replace("。",".")
						.replace("・", ",")
						.replace("~", "l")
				);
		return Json.convert(result);
	}

このリクエストパラメータを引数で取れるところがT2の相当素敵なところだと思います。
sastrutsで開発していた際、一つのActionが大きくなりすぎて、どのExecuteメソッドで、どのActionFormの値を使っているのか、わけがわからなくなることが多かったのです。
ActionFormがプロパティとして宣言されているため、実装時に気をつけないとよく起きていました。
ちなみに文章⇒振り仮名の返還はYahoo!のルビ振りサービスを利用して、UrlFetchでxmlを受け取って、解析し、作成しています。
振り仮名⇒ローマ字の返還はICU4Jを利用しています。

次に通信javascriptです。
jqueryを使ってます。

	$(function(){
		$("[name*=transeform]").click(function(){
			var id = $(this).attr("name").replace("transeform" , "");
			var text = $("#" + id).val();
			transeFormLatin(id , text);
		});
	});
	function transeFormLatin(id , text){
		$.ajax(
			{
				url     : "${t:url('通信先')}",
				type    : "post",
				dataType  : "json",
				data    : {
					"text" : text
				},
				success : function(response , json) {
					$("#" +id + "Alpha").val(response);
				},
				error   : function(xmlHttpReq, status, e) {
					$("#" +id + "Alpha").val("error");
				}
			}
		);
		return false;
	}

であとはボタンを押せばOKって感じです。

もうすこし作りこんだら、サイトの方を公開したいと思います。
しょぼしょぼですが。。。