JerseyMVCの使い方メモ

JAX-RS の仕様に組み込んで欲しいという意見をちらほら見かける、噂の Jersey MVC を試す。

テンプレートには JSP を使い、とりあえず GlassFish で ver 2.0 を試してから、 Tomcat で 2014/01/26 現在最新の ver 2.5.1 を試してみる。

#環境
##AP サーバ
###GlassFish
4.0

###Tomcat
7.0.42

#GlassFish で ver 2.0 を試す
##Jersey MVC JSP はバンドルされている
GlassFish 4.0 には、 Jersey MVC JSP の 2.0 がバンドルされているので、それを利用する。

```text:
glassfish\modules>dir /b jersey-mvc*
jersey-mvc-connector.jar
jersey-mvc-jsp.jar          ← MVC JSP がバンドルされてる
jersey-mvc.jar
```

##依存関係の宣言
```groovy:build.gradle
dependencies {
    providedCompile 'javax:javaee-api:7.0'
    providedCompile 'org.glassfish.jersey.ext:jersey-mvc-jsp:2.0'
}
```

ビルドには Gradle を使うので、その依存関係の宣言。

##HelloWorld
とりあえず Hello World する。

###Application クラスを作成する
```java:MyApplication.java
package sample.jersey.mvc.jsp;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.MvcFeature;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;

public class MyApplication extends ResourceConfig {
    
    public MyApplication() {
        this.packages(MyApplication.class.getPackage().getName())
            .register(JspMvcFeature.class);
    }
}
```

###web.xml に Filter を登録する
```xml: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_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>Jersey MVC JSP Sample</display-name>
  
  <filter>
    <filter-name>Jersey MVC JSP Filter</filter-name>
    <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
    <init-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>sample.jersey.mvc.jsp.MyApplication</param-value>
    </init-param>
    <!-- pass to next filter if Jersey/App returns 404 -->
    <init-param>
      <param-name>jersey.config.servlet.filter.forwardOn404</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
    <filter-name>Jersey MVC JSP Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
```

###リソースクラスを作成する
```java:MyResource.java
package sample.jersey.mvc.jsp;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("hello-mvc")
public class MyResource {
    
    @GET
    public Viewable helloWorld() {
        return new Viewable("/hello-mvc");
    }
}
```

###View（JSP）を作成する
```jsp:hello-mvc.jsp
<html>
  <head>
    <title>Hello Jersey MVC</title>
  </head>
  <body>
    <h1>Hello Jersey MVC !!</h1>
  </body>
</html>
```

配置する場所は、 Web コンテンツフォルダ直下。

###動作確認する
アプリを GlassFish に配備して、ブラウザから `http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc` にアクセスする。

__※ホスト名、ポート、コンテキストルートは適宜読み替えてください。__

![JerseyMVCHelloWorld.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c25905d1-7dc3-81e2-b594-3256781fc008.jpeg)

###説明
####Application クラスで MVC JSP を有効にする
単純に JAX-RS を使う場合は、 `javax.ws.rs.core.Application` を継承したクラスを作成するが、 Jersey MVC を使う場合は Jersey が提供している Application クラスのサブクラスである `ResourceConfig` を継承して Application クラスを作成する。

`ResourceConfig` には JAX-RS や Jersey 用の設定を簡単に定義するためのメソッドとかが用意されている。

Jersey MVC を有効にするには、このクラスのコンストラクタで以下のように実装する。

```java:MyApplication.java
    public MyApplication() {
        this.register(JspMvcFeature.class);
    }
```

JSP 以外にも標準だと Mustache と Freemarker がサポートされているみたいだけど、今はとりあえず JSP で。

####MVC JSP を使う場合は web.xml が必須

[18.6.3. JSP | Chapter 18. MVC Templates](https://jersey.java.net/documentation/latest/mvc.html#d0e13219)
>Limitations of Jersey JSP MVC Templates
>
>Jersey web applications that want to use JSP templating support should be registered as Servlet filters rather than Servlets in the application's web.xml. The web.xml-less deployment style introduced in Servlet 3.0 is not supported at the moment for web applications that require use of Jersey MVC templating support. 
>
>【訳】
>Jersey JSP MVC テンプレートの制約
>
>Jersey Web アプリケーションで JSP テンプレートを使いたい場合は、 ServletFilter をアプリケーションの web.xml に登録しないといけません。Servlet 3.0 で紹介されている web.xml 無しのデプロイスタイルは、 Jersey MVC テンプレートサポートを使用する Web アプリケーションでは今のところサポートされていません。

ということで、 Filter を web.xml に定義する必要がある。

####リソースメソッドで Viewable を返すようにする
表示する View を指定するため、リソースメソッドで Viewable クラスのインスタンスを返すようにしている。

```java:MyResource.java
    @GET
    public Viewable helloWorld() {
        return new Viewable("/hello-mvc");
    }
```

Viewable のコンストラクタに渡している文字列が、 View を指定するためのパス。

`/` で始まっているので、 Web コンテンツフォルダのルートからの絶対パスになる。

末尾に拡張子 `.jsp` が無い場合は Jersey が勝手に `.jsp` を末尾に付けてテンプレートファイルを検索するので、 `/hello-mvc` は、 Web コンテンツフォルダ直下の `hello-mvc.jsp` ファイルを表すことになる。

##View に値を渡す
```java:MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("hello-mvc")
public class MyResource {
    @GET
    public Viewable helloWorld() {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");
        
        return new Viewable("/hello-mvc", model);
    }
}
```

```jsp:hello-mvc.jsp
<html>
  <head>
    <title>Hello Jersey MVC</title>
  </head>
  <body>
    <h1>Model : ${it.hoge}, ${it.fuga}, ${it.piyo}</h1>
  </body>
</html>
```

![model.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/29193ec5-4555-2e7b-2a20-850e3b7a7be9.jpeg)

Viewable のコンストラクタの第二引数に指定した値は、 JSP 上で `it` で参照できるようになる。

##View の解決方法
###絶対パス指定
`/` で始まるパスは __絶対パス__ と判断され、 Web コンテンツフォルダのルートからのパスとして解釈される。

###相対パス指定
`/` で始まらないパスは __相対パス__ と判断され、 `<Web コンテンツフォルダ>/<リソースクラスのパッケージと同じ階層>/<リソースクラス名>/` からの相対パスとして解釈される。

例えば次のようなリソースクラスがあったとする。

```java:HogeResource.java
package hoge.fuga;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("piyo")
public class PiyoResource {
    
    @GET
    public Viewable get() {
        return new Viewable("view-template");
    }
}
```

`get()` メソッドが返している `Viewable` で指定されている `view-template` は、 `<Web コンテンツフォルダ>/hoge/fuga/PiyoResource/view-template.jsp` と解釈される。


##@Template アノテーションで View を指定する
```java:MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Template;

@Path("hello-mvc")
public class MyResource {
    
    @GET
    @Template(name="/hello-mvc")
    public Map<String, String> helloWorld() {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");
        
        return model;
    }
}
```

Viewable を return する代わりに、メソッドに `@Template` アノテーションを付与して、 name 属性※で View を指定する方法もある。

この場合、リソースメソッドの戻り値が View に渡される。

##リソースクラスに @Template アノテーションを設定する
```java:MyResource.java
package sample.jersey.mvc.jsp;

import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Template;

@Path("hello-mvc")
@Template
public class MyResource {
    
    public String getHoge() {
        return "HOGE";
    }
}
```

```jsp:Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/index.jsp
<html>
  <head>
    <title>index.jsp</title>
  </head>
  <body>
    <h1>${it.hoge}</h1>
  </body>
</html>
```

`@Template` アノテーションをクラスに付与すると、暗黙的なルールがいくつも追加される。

リソースメソッドが１つも定義されていないが、この状態で `http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc` にアクセスすると、次のように画面が開く。

![templateをクラスに指定.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e4103c5c-938e-bc47-1dce-92bf70918d19.jpeg)

`@Template` アノテーションを付与されたリソースクラスには、 Jersey が自動的に以下のリソースメソッドを追加する。

```java:暗黙的に追加されるリソースメソッド
@GET
public Viewable get() {
    return new Viewable("index", this);
}
```

このため、リソースメソッドを定義することなく画面遷移が実現できている。

Model にはリソースクラスのインスタンスが指定されている（`this`）ので、 JSP 上での `it` はリソースインスタンスを指している。

さらに、 Jersey は以下のようなリソースメソッドも自動で追加する。

```java:暗黙的に追加されるリソースメソッド２
@GET
@Path("{implicit-view-path-parameter}")
public Viewable get(@PathParameter("{implicit-view-path-parameter}") String template) {
    return new Viewable(template, this);
}
```

つまり、例えば `Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/` に次のような jsp ファイルを配置し、

```jsp:Webコンテンツフォルダ/sample/jersey/mvc/jsp/MyResource/implicit-view.jsp
<html>
  <head>
    <title>implicit-view.jsp</title>
  </head>
  <body>
    <h1>Implicit View</h1>
  </body>
</html>
```

ブラウザから `http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc/implicit-view` にアクセスすると、↑の JSP が表示される。

![暗黙的なView.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6f94ea7a-de93-4a79-df08-bfb659b620cb.jpeg)

##View テンプレートを検索するときのルートを変更する
```java:MyApplication.java
package sample.jersey.mvc.jsp;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
import org.glassfish.jersey.server.mvc.jsp.JspProperties;

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        this.packages(MyApplication.class.getPackage().getName())
            .register(JspMvcFeature.class)
            .property(JspProperties.TEMPLATES_BASE_PATH, "jsp");
    }
}
```

Application クラスで `property(String, String)` メソッドを使って `JspProperties.TEMPLATES_BASE_PATH` の値を設定すれば、 View のテンプレートファイルを検索するときのルートを変更することができる。

上記実装の場合、ルートは `<Web コンテンツフォルダ>/jsp` に変更される。

##CDI, EJB との連携
特に設定などはなしに、リソースクラスは CDI, EJB と連携できる。

```java:MyService.java
package sample.jersey.mvc.jsp;

public class MyService {
    
    public void method() {
        System.out.println("MyService method.");
    }
}
```

```java:MyEjb.java
package sample.jersey.mvc.jsp;

import javax.ejb.Stateless;

@Stateless
public class MyEjb {
    
    public void method() {
        System.out.println("MyEjb method.");
    }
}
```

```java:MyResource.java
package sample.jersey.mvc.jsp;

import javax.ejb.EJB;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("hello-mvc")
public class MyResource {
    
    @Inject
    private MyService service;
    @EJB
    private MyEjb ejb;
    
    @GET
    public void method() {
        this.service.method();
        this.ejb.method();
    }
}
```

↓`http://localhost:8080/jersey-mvc-jsp-sample/hello-mvc` にアクセスしたときの標準出力。

```text:出力結果
情報: MyService method.
情報: MyEjb method.
```

#Tomcat で ver 2.5.1 を試す

Tomcat には Jersey はバンドルされてないので、最新版の 2.5.1 を試す。
基本的な使い方は GlassFish で試したのと同じ実装で問題ない。

ここでは、 ver 2.0 ではサポートされてなかった使い方について試す。

##依存関係の宣言
```groovy:build.gradle
dependencies {
    providedCompile 'org.apache.tomcat:tomcat-servlet-api:7.0.42'
    compile 'org.glassfish.jersey.ext:jersey-mvc-jsp:2.5.1'
}
```

##@ErrorTemplate アノテーションで例外発生時の遷移先を指定する
```java:
package sample.jersey.mvc.jsp;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.ErrorTemplate;

@Path("mvc")
public class MyResource {

    @GET
    @ErrorTemplate(name="/error.jsp")
    public void get() throws Exception {
        throw new Exception("test exception");
    }
}
```

```jsp:error.jsp
<html>
  <head>
    <title>Error Page</title>
  </head>
  <body>
    <h1>Error Page</h1>
  </body>
</html>
```

__リソースメソッドにアクセスした結果__

![エラーページ.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e1334f10-e827-5366-79cd-e9c8628ebaad.jpeg)

`@ErrorTemplate` アノテーションを使えば、例外が発生したときの遷移先を指定できる。

`@ErrorTemplate` アノテーションは、 ver 2.3 で利用できる。

##Model の参照
```java:MyResource.java
package sample.jersey.mvc.jsp;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.glassfish.jersey.server.mvc.Viewable;

@Path("mvc")
public class MyResource {

    @GET
    public Viewable get() throws Exception {
        Map<String, String> model = new HashMap<>();
        model.put("hoge", "HOGE");
        model.put("fuga", "FUGA");
        model.put("piyo", "PIYO");
        
        return new Viewable("/test.jsp", model);
    }
}
```

```jsp:test.jsp
<html>
  <head>
    <title>Jersey MVC</title>
  </head>
  <body>
    <h1>Model : ${model.hoge}, ${model.fuga}, ${model.piyo}</h1>
  </body>
</html>
```

![modelで参照.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/648e0637-d8ed-4261-0cfb-9171152c7ccd.jpeg)

View に渡したモデルは、 `it` だけでなく、 `model` でも参照できるようになっている。

#2.6 より前の JSP モジュールには文字コードの処理でバグがある
どうも 2.6 より前の JSP モジュールはレスポンスを書き出すときに文字コードを明示していないため、環境デフォルトの文字コードを使ってしまっているらしい。

[Jersey に上げられているバグ報告 > No way to specify an output encoding of jersey-mvc-jsp extension module (Always default encoding is used)](https://java.net/jira/browse/JERSEY-2188)

このため、例えば Shift_JIS がデフォルト文字コードの Windows 環境下だと、たとえ JSP で

```jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
```

というふうに文字コードをしっかり指定していても、文字化けが発生してしまう。

このバグは 2.6 で fix しているみたいなので、それ以前のバージョンを使用する場合は以下のいずれかの対応が必要になる。

- 環境のデフォルトエンコーディングに合わせて JSP の文字コードを指定する。
- AP サーバーを起動するときの JVM オプションで `-Dfile.encoding=<JSP の文字コード>` を指定する。

#参考
- [Chapter 18. MVC Templates](https://jersey.java.net/documentation/latest/mvc.html)
- [Jersey 2.0 User Guide](https://jersey.java.net/documentation/2.0/index.html)