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

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
        ...
    }
}

Path Parameter

e.g. ProductListAction#index()

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

e.g. ProductListAction#search()

e.g. switch path variable and method name in URL @Java
    // /product/list/mystic/search/ (sea=mystic)
    @Execute(urlPattern = "{}/@word")
    public HtmlResponse search(String sea) {
        ...
    }

e.g. ProductListAction#searchShow()

e.g. switch path variable and method name in URL @Java
    // /product/list/mystic/search/oneman/show/ (sea=mystic, land=oneman)
    @Execute(urlPattern = "{}/@word/{}/@word")
    public HtmlResponse searchShow(String sea, String land) {
        ...
    }

Action Form (for POST, GET parameter)

e.g. accept POST parameter by action form @Java
    @Execute
    public HtmlResponse signin(ProductSearchForm form) { // POST (or also GET)
        ...
    }
e.g. both Path 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<ProductResult> product() {
        ProductResult result = ...
        return asJson(result);
    }

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>

File Upload

You can define MultipartFormFile property in your form class.

e.g. MultipartFormFile in Form class @Java
@Required
public MultipartFormFile file;

But first you need to set commons-fileupload in your dependencies and set MultipartRequestHandler at assistant director. (because LastaFlute does not have upload engine)

HTTP Method Restriction

You can restrict mapping by method name that has HTTP method.

e.g. GET request only @Java
    @Execute
    public HtmlResponse get$index(ProductSearchForm form) {
        ...
    }
e.g. POST request only @Java
    @Execute
    public HtmlResponse post$index(ProductSearchForm form) {
        ...
    }

Action Transaction

Action's @Execute method is already in transaction.

Success
Automatically Committed
Exception
Automatically Rolled-back

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);
}
もっとバリデーション知りたい
見通しのValidation
トランザクションのこと知りたい
Action の Transaction
JSON APIサーバー作りたい
素早さのJSON API
なんだかんだ JSP を使いたい
LastaFlute の JSP
いやいや Thymeleaf を使いたい
LastaFlute の Thymeleaf
ログイン認証したい
組み込みログイン制御 (Login Control)
Action定義を一括でUnitTest
Action定義のテスト (DefTest)
Action周りいろいろと微調整したい
ActionやJSONの微調整
Actionで共通の前後処理を入れたい
気軽な前後処理 ActionHook
Actionをどう実装したらいいの?
Actionの実装デザイン
どんなJSONにしたらいいの?
JSON APIのJSONデザイン
Master/SlaveDBに対応したい
LastaFluteのMaster/SlaveDB

☆ 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は両方)