JerseyMVCで静的ファイルをリソースクラスの対象から除外する

【前提条件】

[環境]
[参考資料]

JerseyMVC Template
Jersey

【概要】

JerseyMVCで静的ファイルをJAX-RSのリソースから除外する方法についてです。

ApplicationPathアノテーションにパスを設定すると
そのパスより下のサブディレクトリが
すべてJAX-RSのリソースとして扱われます。

今回は静的ファイルを除外する方法について調べてみました。

【パスでなんとかする】

簡単に思い浮かぶ方法としては
ApplicationPathアノテーションに指定するパスで工夫するというのがあります。

ApplicationPathに「/service」などの値を設定して、
静的ファイルは別のパスを設定する方法です。

この方法だと画面を表示するためのURLに
全て「/service」をつけることになるので、
URLが長くなってしまいます。

【静的リソースのパスを指定する】

今回のメインとなる内容です。

JerseyMVCでは特定のURLを静的ファイルとして扱い、
JAX-RSのリソースから除外する設定があります。

設定はweb.xmlで行います。

【web.xml

web.xmlにfilterの設定を行います。

<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">

    <filter>
        <filter-name>JerseySettingFilter</filter-name>
        <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>jp.ne.glory.web.ApplicationSetting</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
            <param-value>/(css|js)/.*</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>JerseySettingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

ServletContainerをフィルターとして登録します。

javax.ws.rs.Applicationはプロパティの名前と同じクラスを継承したクラスを指定します。
JerseyMVCを動かす場合はorg.glassfish.jersey.server.ResourceConfigを
継承して作成していると思うので、そのクラスを設定します。

jersey.config.servlet.filter.staticContentRegexに
静的ファイルとして取り扱いたいURLを指定します。
URLの指定はその名のとおり正規表現で指定します。

【ResourceConfig】

package jp.ne.glory.web;

import javax.ws.rs.ApplicationPath;
import jp.ne.glory.web.framework.ThymeleafViewProcessor;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.MvcFeature;

@ApplicationPath("/")
public class ApplicationSetting extends ResourceConfig {

    public ApplicationSetting() {

        packages(this.getClass().getPackage().getName());

        register(ThymeleafViewProcessor.class);

        register(MvcFeature.class);

        register(LoggingFilter.class);
    }
}

ApplicationSettingではApplicationPathに「/」を指定し、
全てのURLをJAX-RSのリソースクラスとして扱うようにします。

【静的ファイルの階層】

静的ファイルの階層は下記のようにします。


【そのほかのファイル】

基本的に前回と同じです。

[テンプレートプロセッサ]
@Provider
public class ThymeleafViewProcessor implements TemplateProcessor<String> {

    @Context
    private HttpServletRequest request;

    @Context
    private HttpServletResponse response;

    @Context
    private ServletContext servletContext;

    private final TemplateEngine templateEngine;

    public ThymeleafViewProcessor() {

        TemplateResolver resolver = new ServletContextTemplateResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setCacheTTLMs(3600000L);

        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(resolver);
    }

    @Override
    public String resolve(String name, MediaType mediaType) {

        return name;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {

        WebContext context = new WebContext(request, response, servletContext);

        context.setVariable("it", viewable.getModel());

        Writer writer = new OutputStreamWriter(out);
        templateEngine.process(templateReference, context, writer);

        writer.flush();
    }
}
[リソースクラス]
@Path("top")
public class Top {

    /**
     * 画面を表示する.
     *
     * @return ビューオブジェクト
     */
    @GET
    @Produces(MediaType.TEXT_HTML)
    public Viewable view() {

        final TopInfo topInfo = new TopInfo(LocalDateTime.now(), "Taro", "Yamada");

        return new Viewable("/top/top", topInfo);
    }
}
[Bean(TopInfo)]
public class TopInfo {
    public final LocalDateTime now;

    public final String lastName;

    public final String firstName;

    public TopInfo(final LocalDateTime now, final String lastName, final String firstName) {
        this.now = now;
        this.lastName = lastName;
        this.firstName = firstName;
    }
}
[HTML]
<!DOCTYPE html>
<html>
    <head>
        <title>Top Page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link rel="stylesheet" href="../../../css/base.css" th:href="@{/css/base.css}" />
    </head>
    <body>
        <div th:text="${it.now}"></div>
        <div th:text="${it.lastName}"></div>
        <div th:text="${it.firstName}"></div>
    </body>
</html>
[CSS]
body {
    margin : 0px;
    padding : 0px;
    background-color : lightsteelblue;
    text-align: center;
}

【動かして見る】

warを作ってhttp://localhost:8080/hoge/topにアクセスします。
hogeにはコンテキストパスを指定します。

↓のような画面が表示されます。

作成したCSSが適用されていることがわかります。

【まとめ】

個人的にはResourceConfigのプロパティ設定で
除外できれば良かったなと思うのですが、できないようです。
もしかしたら、自分の設定が悪くてできていない可能性もありますが・・・

それでもweb.xmlに設定書くだけなので、簡単に設定できるのが良いですね。