LastaFlute の Action

URL Mapping

Actionを見れば、URLがおおよそわかる。

Mapping Convention (Big convention)

Request URL
Action class + Execute method

Mapping Example

ProfilePasswordAction#change()
/profile/password/change/
ProductAction#list()
/product/list/
ProductListAction#index()
/product/list/
MypageAction#index()
/mypage/
RootAction#index()
/ ※Special Action means URL '/'

Customize Mapping

You can also customize it...

Action Package

Actionの名前で、置き場所がおおよそわかる。

Package Convention

Application Package
[your domain].app e.g. org.docksidestage.app
Web Package
[Application Root].web e.g. org.docksidestage.app.web
Action's Package
*you can use Action's prefix package

Package Example

You can choose several pattern like this: e.g. ProfilePasswordAction

  • [Application Root].web.ProfilePasswordAction#change()
  • [Application Root].web.profile.ProfilePasswordAction#change() *recommended
  • [Application Root].web.profile.password.ProfilePasswordAction#change()
e.g. Action Class Package Location @Directory
org.docksidestage
 |-app // Application Package
 |  |-logic
 |  |-web // Web Package
 |     |-product
 |     |  |-ProductListAction
 |     |  |-ProductPurchaseAction
 |     |-profile
 |     |  |-ProfilePasswordAction
 |     |  |-ProfileWithdrawalAction
 |     |-mypage
 |     |  |-MypageAction
 |     |-RootAction
 |-dbflute
 |  |-allcommon
 |  |-bsbhv
 |  |-...
 |-mylasta
 |  |-action
 |  |-direction
 |  |-...

make Action Class

INとOUTは、引数と戻り値で。

Action Defintion

Super Class
extends [App]BaseAction
Behavior DI
instance variable with @Resource
Execute Method
public method with @Execute
e.g. Action Class Definition @Java
public class ProductListAction extends DocksideBaseAction { // Super Class

    @Resource
    private ProductBhv productBhv; // Behavior DI

    @Execute
    public HtmlResponse index() { // Execute Method
        ...
    }
}

URL Parameter

e.g. ProductListAction#index()

e.g. accept URL parameter as required argument @Java
    @Execute
    public HtmlResponse index(int pageNumber) { // /product/list/3
        ...
    }
e.g. accept URL parameter as optional argument @Java
    @Execute
    public HtmlResponse index(OptionalThing<Integer> pageNumber) {
        pageNumber.ifPresent(() -> { // /product/list/3
            ...
        }).orElse(() -> { // /product/list/
            ...
        });
    }
e.g. accept URL parameter with more URL @Java
    // /product/list/mystic/ikspiary/oneman/ (sea=mystic, land=oneman)
    @Execute(urlPattern = "{}/ikspiary/{}")
    public HtmlResponse index(String sea, String land) {
        ...
    }

Action Form (for POST, GET parameter)

e.g. accept POST parameter by action form @Java
    @Execute
    public HtmlResponse doSignin(ProductSearchForm form) { // POST (or also GET)
        ...
    }
e.g. both URL parameter and GET parameter @Java
    // e.g. /.../list/3?favoriteCode=sea&nextName=land
    @Execute
    public HtmlResponse index(int pageNumber, ProductSearchForm form) {
        ...
    }

You can use Validator Annotation for basic validation. e.g. Required, Max... (then you should call the validate() method in execute method of action)

e.g. validator annotation in form @Java
public class ProductEditForm {

    @Required
    public String productName;
}

And you can basic native type for bean properties. e.g. Integer, LocalDate, CDef...

e.g. native type for properties in form @Java
public class ProductSearchForm {

    public Integer productId;
    public String productName;
    public CDef.ProductStatus productStatus;
}

JSON Body (for JSON in request body)

e.g. accept JSON in request body by JSON body @Java
    @Execute
    public JsonResponse<ProductBean> index(ProductSearchBody body) {
        ...
    }

It can be treated as action form, e.g. can use validation, native type properties

e.g. body for product search @Java
public class ProductSearchBody {

    public Integer productId;
    public String productName;
    @Required
    public CDef.ProductStatus productStatus;
}

Action Response

HtmlResponse
asHtml(path_...) or redirect(...class)
JsonResponse
asJson(bean)
StreamResponse
asStream(ins)
e.g. HtmlResponse @Java
    @Execute
    public HtmlResponse index() {
        ...
        return asHtml(path_MyPage_MyPageJsp);
    }

    @Execute
    public HtmlResponse doUpdate() {
        ...
        return redirect(MypageAction.class);
    }
e.g. JsonResponse @Java
    @Execute
    public JsonResponse<ProductBean> product() {
        ProductBean bean = ...
        return asJson(bean);
    }

Form/Body Validation

You can use Hibernate Validator's annotations in form or body.

e.g. validator annotation in form @Java
public class ProductEditForm {

    @Required
    public String productName;
}

And you should call in your action.

e.g. validation as HTML response @Java
@Execute
public HtmlResponse index(OptionalThing<Integer> pageNumber
                        , ProductSearchForm form) {
    validate(form, messages -> {}, () -> {
        return asHtml(path_Product_ProductListJsp);
    });
    ...
}
e.g. validation as JSON response @Java
@Execute
public JsonResponse<SearchPagingBean<ProductRowBean>> index(
                OptionalThing<Integer> pageNumber
              , ProductSearchBody body) {
    validate(form, messages -> {});
    ...
}

Specify more validation manually at second argument.

e.g. more validation @Java
@Execute
public JsonResponse<SearchPagingBean<ProductRowBean>> index(
                OptionalThing<Integer> pageNumber
              , ProductSearchBody body) {
    validate(form, messages -> {
        if (body.productName == null) {
            messages.addConstraintsRequiredMessage("productName");
        }
    });
    ...
}

DoubleSubmit Token

You can call saveToken() and verifyToken() in action.

e.g. double submit token @Java
@Execute
public HtmlResponse index() {
    saveToken();
    ...
}
@Execute
public HtmlResponse update() {
    verifyToken(() -> {
        return asHtml(path_Product_ProductListJsp);
    });
    ...
}

And keep the token in HTML as hidden field like this:

e.g. saved token in Thymeleaf template @Html
<form th:action="@{/member/edit/}" action="#" method="post">
    <input type="hidden" la:token="true"/>
    ...
</form>

Example Code

HTML response style

e.g. Action for URL '/product/list/3' as HTML response @Java
@Execute
public HtmlResponse index(OptionalThing<Integer> pageNumber
                        , ProductSearchForm form) {
    validate(form, messages -> {}, () -> {
        return asHtml(path_Product_ProductListJsp);
    });
    PagingResultBean<Product> page = productBhv.selectPage(cb -> {
        cb.setupSelect_ProductStatus();
        cb.query().setProductName_LikeSearch(form.productName, op -> op.likeContain());
        cb.query().addOrderBy_ProductName_Asc();
        cb.paging(getPagingPageSize(), pageNumber.orElse(1));
    });
    List<ProductRowBean> beans = page.mappingList(product -> {
        return mappingToBean(product);
    });
    return asHtml(path_Product_ProductListJsp).renderWith(data -> {
        data.register("beans", beans);
        registerPagingNavi(data, page, form);
    });
}

JSON response style

e.g. Action for URL '/product/list/3' as JSON response @Java
@Execute
public JsonResponse<SearchPagingBean<ProductRowBean>> index(
                OptionalThing<Integer> pageNumber
              , ProductSearchBody body) {
    validate(body, messages -> {});
    ListResultBean<Product> page = productBhv.selectPage(cb -> {
        cb.setupSelect_ProductStatus();
        cb.query().setProductName_LikeSearch(form.productName, op -> op.likeContain());
        cb.query().addOrderBy_ProductName_Asc();
        cb.paging(getPagingPageSize(), pageNumber.orElse(1));
    });
    SearchPagingBean<ProductRowBean> bean = createPagingBean(page);
    return asJson(bean);
}
LastaFluteでJSPの利用
LastaFlute JSP

☆ Actionの作り方 ☆ (実際に作ってみよう)

実際に作りながら、細かく実装の流れを理解していくための、Actionの作り方ページがあります。

サーバーサイドHTML
Actionの作り方 (HTMLスタイル)
JSON APIサーバー
Actionの作り方 (JSONスタイル)

サーバーサイドHTMLは、JSP や Thymeleaf など、サーバー側でHTML文字列を作って、それをブラウザに戻して表示します。 JSON APIサーバーは、画面を表示するためのデータをJSON形式でクライアント (e.g. JavaScript, Swift, ...) に戻し、あとは画面描画はクライアントにお任せをします。

もちろん、サーバーサイドHTMLの方でも、Ajax通信でJSONを戻すことはありえるので、実際には一つのアプリで混ざることも多いでしょう。

Exampleだと、マルチプロジェクトの maihama が、docksideプロジェクトがサーバーサイドHTML、hangarプロジェクトがJSON APIサーバーとなっています。 (シングルプロジェクトのharborは両方)