Struts2をやってみる (14) - TypeConverter -

2013-10-01T00:00:00+00:00 Java Struts2

何やらアクションで評価されるパラメーターとか?を指定した型で変換する仕組みが備わってるようで。公式ドキュメント

とりまぁ読みつつ進めてみるかってことで

概要

この型変換の仕組みを利用する方法は3つ

  • com.opensymphony.xwork2.conversion.annotations.Conversion(アノテーション)をActionに指定する
  • アクションクラス名-conversion.propertiesで指定する
  • もしくはクラスパスルートにxwork-conversion.propertiesで指定する

全てやり方的なのは紹介するけど、2と3の違いに関しては「型で指定するか」「変数名的な感じで指定するか」って所なのではと

struts.xml

http://localhost:8080/struts2/top/page/1 的なURLを表現するにはどうすれば良いのかって所としては

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <package name="sample" extends="struts-default">
        <default-action-ref name="top" />

        <action name="top" class="sample.controllers.sample.TopAction" method="top" />

        <action name="top/page/*" class="sample.controllers.sample.TopAction" method="top">
            <!-- /struts2/top/page/1?page=2とかで指定しても作用しないようにするにはexcludeParamsとかでやれば良い? もっと良い方法ないのか -->
            <interceptor-ref name="defaultStack">
                <param name="params.excludeParams">^page</param>
            </interceptor-ref>

            <!-- 上記のマッピングで*に該当する部分を{1}として利用できるのでそれをアクションにパラメーターとしてぶっこむ的な感じ? -->
            <param name="page">{1}</param>

            <!-- パラメーターの型とかで問題になるとinputななぜか出るのでそこら辺はhttpheaderなresult typeを使ってエラーを出してやる -->
            <result name="input" type="httpheader">
                <param name="status">500</param>
                <param name="errorMessage">Internal Server Error</param>
            </result>
        </action>
    </package>
</struts>

/topと/top/page/(.*)的なののマッピングが2つ必要になるだろうと思ってやってるけど、これをひとまとめに出来ないものかと。んまぁそれはおいといて

通常だと<action>で指定出来るname属性にスラッシュを組み込む事は出来ない模様だけど、struts.enable.SlashesInActionNamesな設定をtrueにしておくと認識させる事が出来る模様

Page.java

上記でパラメーターとして認識するpageパラメーターは元はStringになってるはず。それをもとにPageオブジェクトで変換してアクションにマッピングする。でそのクラス

package sample;

import java.io.Serializable;

public class Page implements Serializable {

    private static final long serialVersionUID = 1L;
    private int page;

    public Page(int page) {
        setPage(page);
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getPage() {
        return page;
    }
}

(ry

TopAction.java

package sample.controllers.sample;

import java.util.Date;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;
//import com.opensymphony.xwork2.conversion.annotations.Conversion;
//import com.opensymphony.xwork2.conversion.annotations.TypeConversion;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sample.Page;

/* アノテーションでやるとこういう感じ
@Conversion(
    conversions = {
        @TypeConversion(
            key = "page",
            converter = "sample.converters.PageConverter"
        )
    }
)
*/
public class TopAction extends ActionSupport {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(TopAction.class);

    private Page page;

    private boolean enabled = false;

    private List<integer> id;

    private Date date;

    public String top() throws Exception {
        return NONE;
    }

    public Page getPage() {
        return page;
    }

    public void setPage(Page page) {
        this.page = page;
    }

    public boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        logger.info("enabled: " + enabled);
        this.enabled = enabled;
    }

    public List<integer> getId() {
        return id;
    }

    public void setId(List<integer> id) {
        logger.info("id: " + id);
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        logger.info("date: " + date.toString());
        this.date = date;
    }
}

っていう感じで上記で説明したようにPageオブジェクトで受け取るという形なので、それに伴うセッターメソッドを定義。あと他にも書いてるけどそれは公式ドキュメント上の「Built in Type Conversion Support」セクションに書いてあるようにデフォルトでサポートされている型変換っていうのもちょっと利用してみる的な感じで書いておいた

でこのアクションでPageオブジェクトを変換する仕組みのコンバータークラスを作ってないので作る

PageConverter.java

適当に書いたのでorz (ていうかテストすら書いてない)

package sample.converters;

import java.lang.reflect.Member;
import java.util.Map;

import com.opensymphony.xwork2.conversion.TypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sample.Page;

public class PageConverter implements TypeConverter {

    private static final Logger logger = LoggerFactory.getLogger(PageConverter.class);

    @Override
    public Object convertValue(
        Map<string, Object> context,
        Object target,
        Member member,
        String propertyName,
        Object value,
        @SuppressWarnings("rawtypes")Class toType) {

        String[] values = new String[] {};

        // さっきStringで来るって言ったけどクエリーで指定するとString[]で来るのでそれら辺をちょっと考慮
        if (value instanceof String[]) {
            values = (String[])value;
        } else if (value instanceof String) {
            String str = (String)value;

            if (!StringUtils.isEmpty(str)) values[0] = str;
        }

        int page = 1;

        try {
            for (String v : values) {
                if (StringUtils.isEmpty(v)) continue;
                page = Integer.parseInt(v);
            }
        } catch (NumberFormatException e) {
            logger.error("ERROR", e);
        }

        logger.info("PageConverter: " + page);

        return new Page(page);
    }
}

公式ドキュメントではStrutsTypeConverterっていうのを継承する形でやってますけど、これ実体はTypeConverterインターフェースを実装したものなので、それを使ってやってる

で上記のアクションでアノテーション方式でのをコメントアウトしていたのでプロパティファイルを使って処理する

src/main/resources/sample/controllers/samppe/TopAction-conversion.properties

page = sample.converters.PageConverter

的な感じで作れば良い。方式は<propertyName>=<Converter Class>ってドキュメント書いてるので

src/main/resources/xwork-conversion.properties

アノテーションで指定するか上記のプロパティファイルを利用するなら良い。型で指定するのであれば

sample.Page = sample.converters.PageConverter

的な感じで書けば良い。

以上で必要な

  • Action: TopAction
  • Convert Object: Page
  • Converter: PageCovnerter

あと上記のプロパティファイルを作成が終わったので検証してみる。まぁテスト書いた方が楽なので

TopActionTestCase.java

package sample.actions;

import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.StrutsSpringTestCase;
import org.junit.Test;

import sample.Page;
import sample.controllers.sample.TopAction;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

public class TopActionTestCase extends StrutsSpringTestCase {

    @Test
    public void test_execute() throws Exception {
        // excludePatternにマッチするので作用しない
        request.setParameter("page", "2");

        request.setParameter("enabled", "true");
        request.setParameter("id", new String[] { "1", "2", "3" });
        request.setParameter("date", "2013/10/01");

        // 上記のrequestパラメーター+以下のgetActionProxyをURLで示すと http://localhost:8080/struts2/sample/top/page/999?page=2&enabled=true&id=1&id=2&id=3&date=2013/10/01 的な感じかと

        ActionProxy proxy = getActionProxy("/top/page/999");
        assertThat(proxy.execute(), is(ActionSupport.NONE));
        assertThat(response.getStatus(), is(200));

        TopAction action = (TopAction)proxy.getAction();
        assertThat(action.getEnabled(), is(true));

        assertThat(action.getId(), notNullValue());
        assertThat(action.getId(), contains(1, 2, 3));

        assertThat(action.getDate(), notNullValue());

        assertThat(action.getPage(), instanceOf(Page.class));

        // 上記で書いてるようにクエリーなpageはexcludeParamsで指定しているので作用しない。よってgetPageは999が返ってくる
        assertThat(action.getPage().getPage(), is(2));
    }
}

終わり。まだ微妙な所もあるし色々追記するかと

Struts2をやってみる (15) - struts.patternMatcher Struts2をやってみる (13) -validationインターセプターを抑制する -