JSON-libを使ってXMLHttpRequestとServlet間通信

JSON-libのV1.0が出たみたいですね。
http://json-lib.sourceforge.net/
早速試してみました。AjaxServletJSON通信でCRUD処理を行う・・・という設定。HTMLのFORMは使わずに、全てJSONで送信してサーバ側でBeanに変換し、戻り値は逆にBeanからJSONに変換して返す・・・という前提で考えてみたいと思います。
まずは登録に使うEntityクラス。Doltengのサンプルテーブルからそのまま利用しました。

@Entity
public class Dept {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "deptGenerator")
	@SequenceGenerator(name = "deptGenerator", sequenceName = "SEQ_DEPT", allocationSize = 1)
	private Integer id;
	
	private Integer dept_no;
	
	private String dept_name;
	
	private String loc;
	
	@Version
	private Integer version_no;
}

setter・getterは省略。
続いて、このEntityのCRUD処理を行うServiceクラス

public class DeptServiceImpl implements DeptService {
	
	private EntityManager entityManager;

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public void delete(Dept dept) {
		dept = entityManager.merge(dept);
		entityManager.remove(dept);
	}

	public Dept find(Integer id) {
		return entityManager.find(Dept.class, id);
	}

	public Dept insert(Dept dept) {
		entityManager.persist(dept);
		return dept;
	}

	public Dept update(Dept dept) {
		return entityManager.merge(dept);
	}

}

そして、Servletクラス

public class DeptPage extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6021914337961203461L;

	private DeptService deptService;

	public void setDeptService(DeptService deptService) {
		this.deptService = deptService;
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Integer id = Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1]);
		JSONObject jObj = JSONObject.fromString(getBody(request.getReader()));
		Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class);
		dept.setId(id);
		deptService.delete(dept);
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Dept dept = deptService.find(Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1]));
		response.setContentType("application/javascript; charset=UTF-8");
		JSONObject.fromBean(dept).write(response.getWriter());
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		JSONObject jObj = JSONObject.fromString(getBody(request.getReader()));
		Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class);
		deptService.insert(dept);
		response.setContentType("application/javascript; charset=UTF-8");
		JSONObject.fromBean(dept).write(response.getWriter());
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Integer id = Integer.parseInt(URIUtil.getResources(request.getPathInfo())[1]);
		JSONObject jObj = JSONObject.fromString(getBody(request.getReader()));
		Dept dept = (Dept) JSONObject.toBean(jObj, Dept.class);
		dept.setId(id);
		dept = deptService.update(dept);
		response.setContentType("application/javascript; charset=UTF-8");
		JSONObject.fromBean(dept).write(response.getWriter());
	}
	
	protected String getBody(BufferedReader reader) throws IOException {
		try {
			StringBuilder sb = new StringBuilder();
			String str;
			while ((str = reader.readLine()) != null) {
				sb.append(str);
			}
			return sb.toString();
		} finally {
			reader.close();
		}
	}

}

JSON-libを使えば、JSON-Bean変換は簡単に実行できるんですね。
URIUtilクラスは自作のユーティリティで、今は単純にパスを「/」で分割してるだけです。もっと綺麗にURLからパラメータを取得する仕組みがあればいいんですが・・・
さて、このServletにはServiceクラスがDIされてますが、これはServletをPageクラスとしてSmart Deployさせてるからです。その処理を行っているServletを別に作成してます。内容は以下のとおり

public class SmartServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8268071562015328437L;
	
	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpServlet delegate = getDelegate(request, response);
		if (delegate != null) {
			delegate.service(request, response);
		} else {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
		}
	}

	protected HttpServlet getDelegate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
		
		String servletPath = request.getServletPath();
		String pathInfo = request.getPathInfo();
		if (pathInfo != null && pathInfo.length() > 1) {
			if (pathInfo.indexOf("/", 1) != -1) {
				pathInfo = pathInfo.substring(0, pathInfo.lastIndexOf("/"));
			}
			S2Container container = SingletonS2ContainerFactory.getContainer();
			NamingConvention convention = NamingConvention.class.cast(container.getComponent(NamingConvention.class));
			String pageName = convention.fromPathToPageName(servletPath + pathInfo);
			Class clazz = convention.fromComponentNameToClass(pageName);
			if (clazz != null) {
				Object delegate = container.getComponent(clazz);
				if (delegate instanceof HttpServlet) {
					HttpServlet servlet = HttpServlet.class.cast(delegate);
					if (servlet.getServletConfig() == null) {
						servlet.init(getServletConfig());
					}
					return servlet;
				}
			}
		}
		return null;
	}

}

NamingConventionクラスはServlet用にいじってます。ServletConfigを渡す部分がちょっと微妙ですが・・・本番はCool Deployすれば大丈夫かな??
さて、このサーブレットとやりとりするHTMLが以下のとおり

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>dept</title>
<script type="text/javascript" src="/smartServlet/js/json.js"></script>
<script type="text/javascript">

function createXMLHttp() {
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	} else if (window.ActiveXObject) {
		try {
			return new ActiveXObject("MSXML2.XMLHTTP");
		} catch (e) {
			return new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
	return null;
}

function Dept() {
	this.id = null;
	this.dept_name = null;
	this.dept_no = null;
	this.loc = null;
	this.version_no = null;
	
	this.copy = function(newDept) {
		this.id = newDept.id;
		this.dept_name = newDept.dept_name;
		this.dept_no = newDept.dept_no;
		this.loc = newDept.loc;
		this.version_no = newDept.version_no;
	}
	
	this.save = function() {
		this.id = document.getElementById("dept_id").value;
		this.dept_name = document.getElementById("dept_name").value;
		this.dept_no = document.getElementById("dept_no").value;
		this.loc = document.getElementById("loc").value;
		this.version_no = document.getElementById("version_no").value;
	}
	
	this.load = function() {
		document.getElementById("dept_id").value = this.id;
		document.getElementById("dept_name").value = this.dept_name;
		document.getElementById("dept_no").value = this.dept_no;
		document.getElementById("loc").value = this.loc;
		document.getElementById("version_no").value = this.version_no;
	}
}

function post() {
	var dept = new Dept();
	dept.save();
	dept.id = null;
	var req = createXMLHttp();
	req.open("POST", "/smartServlet/view/dept/", true);
	req.onreadystatechange = function() {
		handler(req);
	}
	req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8");
	req.send(dept.toJSONString());
	
}
function get() {
	var req = createXMLHttp();
	req.open("GET", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true);
	req.onreadystatechange = function() {
		handler(req);
	}
	req.send(null);
	
}

function put() {
	var dept = new Dept();
	dept.save();
	var req = createXMLHttp();
	req.open("PUT", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true);
	req.onreadystatechange = function() {
		handler(req);
	}
	req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8");
	req.send(dept.toJSONString());
}

function del() {
	var dept = new Dept();
	dept.save();
	var req = createXMLHttp();
	req.open("DELETE", "/smartServlet/view/dept/" + document.getElementById("dept_id").value, true);
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			document.getElementById("dept_id").value = "";
			document.getElementById("dept_name").value = "";
			document.getElementById("dept_no").value = "";
			document.getElementById("loc").value = "";
			document.getElementById("version_no").value = "";
		}
	}
	req.setRequestHeader("Content-Type", "text/javascript; charset=UTF-8");
	req.send(dept.toJSONString());
}

function handler(req) {
	if (req.readyState == 4 && req.status == 200) {
		var json = req.responseText;
		var newDept = eval("(" + json + ")");
		var dept2 = new Dept();
		dept2.copy(newDept);
		dept2.load();
	}
}
</script>
</head>
<form action="">
	id:<input type="text" id="dept_id"/><br/>
	dept_name:<input type="text" id="dept_name"/><br/>
	dept_no:<input type="text" id="dept_no"/><br/>
	loc:<input type="text" id="loc"/><br/>
	<input type="hidden" id="version_no">
	<input type="button" value="GET" onclick="get()">
	<input type="button" value="POST" onclick="post()">
	<input type="button" value="PUT" onclick="put()">
	<input type="button" value="DELETE" onclick="del()">
</form>
</html>

json.jsを使ってJavaScriptのオブジェクトをJSON変換して、HTTPのボディに流して送信してます。FORMのパラメータは使ってません。
・・・とまぁ、かなり乱雑な作りですが(汗)、JSONを使えば、JavaScriptとサーバサイドで、オブジェクト単位でやりとり出来るっぽいですね。バリデーション等も、JavaSE6とか使えば、まずはJavaScript側で実装しておいて、サーバサイドでも同じスクリプトを読み込んで共通化する・・・みたいなことも可能かも?
あと、XMLHttpRequestとやりとりする前提なら、Headerも自由に追加できるので、よりHTTPの規約に則った形でWebアプリの細かな制御が出来そうな気がします。HTMLのFORMにはもう縛られる必要はないわけだから、もっとHTTPをフルに活用する前提でWebアプリを考えてみるのも結構面白そうです。