LastaFlute の Action
- URL Mapping
- Action Package
- Action Class
- Action Transaction
- Example Code
- Other Links
- ☆ 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
- 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);
}
Other Links
- もっとバリデーション知りたい
- 見通しの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は両方)