Resteasy + Silverlight2

今までJavaのRESTful WebサービスJAX-RS)の実装としてJerseyを色々調べてきたのですが、このJerseyにはSpringとの連携機能はあるものの、EJB3との連携機能が存在しません。同じJAX-RS実装であるJBossのResteasyを調べてみたところ、こちらはSpringに加えてEJB3との連携機能があるようです。具体的には、EJB3のSessionBeanをそのままJAX-RSのResourceクラスとして登録出来るみたいですね。これなら、Springを使わなくてもJPAや宣言トランザクションを利用することが出来ます。というわけで早速動かしてみました。動作環境はJavaSE6 + JBoss5 CR2です。今回、クライアント側にSilverlightを使ってみたので、Windows環境で動かしました。
まずは今回JAX-RSで扱うリソースにアクセスするJPAのクラス。

package resteasy.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.xml.bind.annotation.XmlRootElement;

@Entity
@XmlRootElement
public class User implements Serializable {

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

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	private String code;
	
	private String password;
	
	private String name;
	
	private Integer age;

	@Version
	private Integer version;

(getter、setter省略)
}

このクラスはJPAのEntityであり、同時にJAX-RSで扱うJAXBのクラスでもあります。なので@Entityに加えて@XmlRootElementも付与しています。
このUserクラスの一覧を取得してXMLで返したいのですが、JAXBの場合一覧のルートを表すクラスを作成する必要があるみたいです。なので、Userの一覧を表すUsersクラスを作成します。

package resteasy.entity;

import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Users {
	
	protected List<User> user;

	public Users() {
		
	}

	public Users(List<User> user) {
		this.user = user;
	}

	@XmlElement
	public List<User> getUser() {
		return user;
	}
}

getUserメソッドに@XmlElementをつけないとうまく動きませんでした。JAXBをそろそろ本格的に勉強しないといけないなと実感・・・
次は、このUserクラスを取得する、JAX-RSのResourceクラスを作成

package resteasy.resource;
import javax.ejb.Local;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import resteasy.entity.Users;

@Local
@Path("/users")
public interface UsersResourceLocal {
	@GET
	@Produces(MediaType.APPLICATION_XML)
	Users getUsers();
}

@Localが付いているので、これはローカルSessionBeanのインターフェイスです。同時に、@PathがついているのでJAX-RSのResourceクラスでもあります。/usersというURIにアクセスされたとき、@GETがついているgetUsersメソッドが呼び出されて、戻り値のUsersがXML変換されて返される・・・という仕組みです。
次はこの実装クラス

package resteasy.resource;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import resteasy.entity.User;
import resteasy.entity.Users;

/**
 * Session Bean implementation class UsersResource
 */
@Stateless
public class UsersResource implements UsersResourceLocal {

	@PersistenceContext
	private EntityManager em;
	
	@SuppressWarnings("unchecked")
	@Override
	public Users getUsers() {
		List<User> users = (List<User>) em.createQuery("SELECT u FROM User u ORDER BY u.id")
			.getResultList();
		return new Users(users);
	}

}

StatelessSessionBeanです。JPAを使ってUser一覧を取得し、その結果を元にUsersを作成して返しています。
JBoss5を使っているので、SessionBeanは自動的に登録されます。JAX-RSを動かすにはweb.xmlで設定する必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>resteasyWeb</display-name>
<context-param>
      <param-name>resteasy.scan</param-name>
      <param-value>true</param-value>
   </context-param>
   
   <context-param>
   	<param-name>resteasy.jndi.resources</param-name>
   	<param-value>resteasy/HelloBean/local,resteasy/UsersResource/local</param-value>
   </context-param>
   
<listener>
      <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
   </listener>

   <servlet>
      <servlet-name>Resteasy</servlet-name>
      <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>Resteasy</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>

resteasy.jndi.resourcesというパラメータをcontext-paramに登録することによって、ResteasyからSessionBeanを読み込めるようになるようです。JNDI名はJBoss独自の規約でしょうか。たしか、GlassFishだとデフォルトではローカルSessionBeanをJNDIに公開していなかった筈。EJB3JAX-RSの組合せなら、現時点ではJBossの方がリードしているようですね。ちなみに、このSessionBean用パラメータは、将来いらなくなる方向でJBoss側が頑張っているみたいです。
ここまで出来たらearプロジェクトとしてJBossにデプロイします。これでサーバサイドは終了。次はクライアントサイドのSilverlightです。まだ勉強はじめたばかりでよくわかってないのですが、とりあえずDataGridを使って単純に一覧表示してみます。まずはXAMLファイル

<UserControl x:Class="SilverlightApplication1.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid x:Name="Users" AutoGenerateColumns="True"/>
    </Grid>
</UserControl>

単純にDataGridを一つ定義しただけです。次は、対となるC#のクラス

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml;
using System.Xml.Linq;

namespace SilverlightApplication1
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(Page_Loaded);

        }

        void Page_Loaded(object sender, RoutedEventArgs e)
        {
            WebClient client = new WebClient();
            Uri uri =
                new Uri("http://localhost:8080/resteasyWeb/users");
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
            client.DownloadStringAsync(uri);
        }

        void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            XDocument doc = XDocument.Parse(e.Result);
            Users.ItemsSource = from user in doc.Descendants("user")
                          select new User {
                              id = (int) user.Element("id"),
                              code = (string) user.Element("code"),
                              password = (string) user.Element("password"),
                              name = (string) user.Element("name"),
                              age = (int) user.Element("age")
                            };
        }




    }
}

XAMLで定義したDataGridのUsersが自動的にC#のクラスのメンバになるのですね。イベントを書くのもVisualStudio任せで非常に楽だし、思っていた以上に使いやすいと感じました。WebClientを使ってJAX-RS側にアクセスしているのですが、ここら辺はAjaxを使ったアクセス方法と非常に似ています。
自分は.netのAPIがよくわかっておらず、XMLからオブジェクト変換する方法がよくわかりませんでした。ここでは、単純なLINQを使ってC#の自作クラスに値を代入しています。
ちなみに、C#で作成したUserクラスがこれ

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightApplication1
{
    public class User
    {
        public int id { get; set; }
        public string code { get; set; }
        public string password { get; set; }
        public int age { get; set; }
        public string name { get; set; }
        public int version { get; set; }
    }
}

やっぱり、Javaと違ってプロパティ記法が出来る分単純ですね・・・
こうして作ったSilverlightのファイルをVSでビルドし、先ほど作ったJBossにWebプロジェクトとしてデプロイします。VisualStudioがTestPage.htmlという表示用ページを作成してくれるので、それにアクセスすると・・・

・・・DataGridすげぇ。C#やってる人なら当たり前の機能なんでしょうけど、オブジェクトのリストを渡してやれば表題とか勝手につけてリストにしてくれるんですね。LINQを使いこなせるようになれば、おおざっぱなデータとしてWebサービスからデータを取得し、順序換えや表示用変換はLINQを使って編集し、DataGridに渡して一覧表示・・・というパターンが使えそうです。
単純取得やアップロードならWebClientクラスを使うようですが、HTTPのメソッドをフル活用するのであればWebRequestクラスを使うみたいですね。次は登録や更新を試せたらいいなと思います。