1. 概要
Groovy/GroovyTemplateを利用して実装されたサンプルアプリケーションです。
2. セットアップ
サンプルアプリケーションのセットアップに関する説明です。
もし、既にiPLAssのInstall and Runまたは開発環境の構築を実行できており、サンプルアプリケーションを動かしたい場合、以下の手順に従って実施してください。
それ以外の場合はInstall and Runから実際に動かしてみることをお勧めします。
- 
サンプルアプリケーションのzipファイルをダウンロードサイトからダウンロードします。
 - 
Install and Runの章で作成したテナント、または開発環境の構築の章でEclipse上で作成したテナント、いずれかを利用してWebアプリケーションを立ち上げ、管理者IDとパスワードでログインします。
 
- 
Admin Consoleの
Packaging機能を利用してzipファイルをインポートします。ダウンロードした「iplass-sample-app-groovy-package.zip」ファイルを、AdminConsoleの「Packaging」ツールで取り込んでください。
デフォルトの設定で「import」ボタンを押下してください。
「iplass-sample-app-groovy-package.zip」ファイルにテナント情報が含まれているため、「import」ボタン押下後に以下の画面が表示されます。 チェックがついている項目は上書きされてしまうため、独自に設定した項目についてはチェックを外してimportしてください。
Importを実行すると「Log」パネルに処理状況が表示されます。処理が終了したタイミングでエラーが発生していないことを確認してください。
新たに作成したロールなどを反映させるために、メタデータのキャッシュを削除してください。
 - 
サンプルアプリでは、全文検索機能(lucene)を利用するため、mtp-service-config.xmlに記載されているINDEXファイルの保存場所をローカル環境の適当な場所に変えます。
<service> <interface>org.iplass.mtp.impl.fulltextsearch.FulltextSearchService</interface> <property name="useFulltextSearch" value="true" /> (1) <property name="maxRows" value="1000" /> <property name="throwExceptionWhenOverLimit" value="true"/> <!-- lucene利用 --> <class>org.iplass.mtp.impl.fulltextsearch.lucene.LuceneFulltextSearchService</class> <property name="directory" value="D:\tmp\lucene" /> (2) <property name="defaultOperator" value="AND" /> <property name="analyzer" value="org.apache.lucene.analysis.ja.JapaneseAnalyzer" /> <property name="indexWriterRAMBufferSizeMB" value="64.0"/> <property name="redundantTimeMinutes" value="10"/> <!-- BinaryReferenceのParse可能な最大文字数 --> <property name="binaryParseLimitLength" value="100000"/> </service>1 useFulltextSearchをtrueに設定します。 2 INDEXファイル保存場所に適当なローカルパスを設定します。 iPLAssのインストーラでWebアプリケーションを起動した場合、ホームディレクトリに作成された設定ファイルを更新してください。
%HOMEPATH%\.iplass\iplass-service-config.xmlEclipseからWebアプリケーションを起動した場合、プロジェクトフォルダにある設定ファイルを更新してください。
src/main/resources/mtp-service-config.xml - 
Admin Consoleの
EntityExplorerでEntity Crawl機能を利用して全文検索のINDEXファイルを作り直します。ToolsのEntityExplorerを選択します。
全文検索を利用する設定になっている場合、「Entity Crawl」タブが表示されます。
A) 任意のEntityのみを対象としてクローリングしたい場合は、リストの対象Entityにチェックを入れて、「Start Crawl」ボタンをクリックしてください。
B) クローリング対象Entityをすべてクローリングしたい場合は「Re Crawl All Entity」ボタンをクリックしてください。
C) クローリングが完了したら、最新のINDEXデータを反映させるために、「Refresh」ボタンをクリックしてください。
 - 
上記起動手順の実施が完了しましたら、サンプルアプリのグローバル設定を確認してください。
 
3. 機能
3.1. Top画面の作成
- 
レイアウトの利用
Top画面の構成は下図のようになっています。画面共通で利用する2つのレイアウト用テンプレートdefaultLayoutとshippingLayoutを用意し、画面のメインエリアは機能に併せて呼びだす形にしています。ここでは画面のメインエリアにtopテンプレートを利用しています。
レイアウト用テンプレート

- 
レイアウト用テンプレート側で、実際のコンテンツを表示したい箇所に
<%renderContent(); %>を記述します。
 
 - 
 - 
レイアウトアクション

- 
レイアウトテンプレートは画面パーツとしての利用になるため、
parts. not direct accessにチェックをいれます。 - 
privilege executeにチェックをいれて特権モードにすると、登録していないユーザーにも公開できます。 
 - 
 - 
Top画面アクション


- 
Top画面のテンプレートでは、レイアウトアクションに
defaultLayoutを指定しています。 
 - 
 
3.2. Utility Class
ここでは、Admin Consoleで作成できる UtilityClass の利用方法を解説します。

- 
CartBeanクラスを例として説明していきます。CartBeanはオレンジ色の部分で利用されています。
※ワークフロー起動はEnterprise Editionの機能です。
- 
UtilityClassを作成する際には以下を実装するようにしてください。
UtilityClass名
samples.ec01.bean.CartBean
package samples.ec01.bean; (1) import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import samples.ec01.utils.Consts; class CartBean implements Serializable { private static final long serialVersionUID = 8770008117231095046L; (2) // カートの商品リスト @Valid List<CartItem> cartItems = new ArrayList<CartItem>(); // カートの総額値段 long totalPrice = 0; // コンストラクタ public CartBean() { } // カートに商品を追加 synchronized public void addCartItem(String productId, long price) { for (CartItem item : this.cartItems) { // 商品が既に追加されていたらValueを1増加 if (item.productId == productId) { item.value = item.value + 1; totalPrice += price; return; } } // 商品が無かった場合には初期値1が入ったCartItemを追加 this.cartItems.add(new CartItem(productId)); totalPrice += price; } // カートの商品を削除 synchronized public void removeCartItem(String productId, long price) { for (int index = 0; index < this.cartItems.size(); index++) { CartItem item = this.cartItems.get(index); if (item.productId == productId) { this.cartItems.remove(index); totalPrice = totalPrice - price * item.value; return; } } } ----------------------------------------以下略----------------------------------------1 package階層はUtilityClassを作成した階層と同一にします。 2 Serializableを実装する場合(作成したクラスをSessionに格納する場合)、serialVersionUIDを忘れずに定義してください。  - 
UtilityClassの利用
新たに商品をカートに追加するコマンドを例に、UtilityClassの利用方法を説明します。カートに入れるコマンド
Commandクラス
samples/ec01/cart/InputCartInfoCommand
import org.iplass.mtp.entity.LoadOption; import samples.ec01.bean.CartBean; (1) def productId = request.getParam("productId"); def cartBean = request.getSession().cartBean; (2) if (cartBean == null) { println "セッションにカートなし"; println "セッションに新しいカート作成"; cartBean = new CartBean(); request.getSession().cartBean = cartBean; } else { println "セッションにカートあり"; println ":総計:${cartBean.totalPrice}"; } def product = em.load(productId, "samples.ec01.products.Product", new LoadOption().localized()); def price = product.price; cartBean.addCartItem(productId, price); println ":総計2: ${cartBean.totalPrice}"; request.totalAmount = cartBean.getTotalAmount(); "SUCCESS";1 UtilityClassで作成したクラスも他のクラスと同じように利用できます。 2 このようにSessionに格納して利用する場合には、Serializableを実装しないと不整合を起こす原因となる可能性があります。  - 
補足: WebApiでの設定
Cartオブジェクトは同期をとって利用したいため、
Synchronize on Sessionを使用しています。
 
 - 
 
3.3. Bean/Bean Validation
iPLAssのBean/Bean Validation機能を利用する場合、バリデーションチェックしたいBeanクラスのプロパティにアノテーションをつけることで、バリデーションが自動で実行されます。また、バリデーション結果を画面に表示できます。
サンプルアプリの問い合わせ登録画面で入力された値に対してバリデーションが実行される機能を例として説明していきます。
- 
必要なUtilityClassを作成します。
Admin ConsoleのUtilityClassの設定を開きます。
- 
カスタムのバリデーショングループ。
 - 
JavaBeanクラス。※ このサンプルではJavaライブラリに既存のバリデーションとカスタムのバリデーショングループを利用しています。
 - 
org.iplass.mtp.command.beanmapper.BeanParamMapperのラッパークラスです。UtilityClass名
samples.ec01.utils.MapperSelector
package samples.ec01.utils; import org.iplass.mtp.command.beanmapper.BeanParamMapper; public abstract class MapperSelector { // CartBean Mapper private static final BeanParamMapper cartMapper = new BeanParamMapper().withValidation().enableAutoGrow() .whitelistPropertyNameRegex("cartItems\\[\\d*\\]\\.productId|cartItems\\[\\d*\\].value"); (1) // InquiryBean Mapper private static final BeanParamMapper inquiryMapper = new BeanParamMapper().withValidation() .whitelistPropertyNameRegex("^(mail|content|familyName(Kana)?|firstName(Kana)?)\$"); // UserBean Mapper private static final BeanParamMapper userMapper = new BeanParamMapper().withValidation() .whitelistPropertyNameRegex("^(userId|mail|familyName(Kana)?|firstName(Kana)?)\$"); // ShippingInfo Mapper private static final BeanParamMapper shippingInfoMapper = new BeanParamMapper().withValidation() .whitelistPropertyNameRegex("^(userId|mail|address|tel|familyName(Kana)?|firstName(Kana)?)\$"); public static select(String name) { if (name == "cartBean") { return cartMapper; } else if (name == "inquiryBean") { return inquiryMapper; } else if (name == "userBean"){ return userMapper; } else if (name == "shippingBean"){ return shippingInfoMapper; } else { return null; } } }1 BeanParamMapperの初期化処理。 
※ whitelistPropertyNameRegexメソッドにセット可能な項目の正規表現式を指定します。 
 - 
 - 
バリデーションエラーメッセージを定義する
Bean Validationエラーメッセージはメッセージマネージャに定義し、多言語利用が可能です。 - 
コマンドクラスでBean Validationを利用する

Command名
samples/ec01/inquiry/RegistInquiryCommand
import jakarta.validation.groups.Default; import org.iplass.mtp.command.beanmapper.BeanParamMapper; import org.iplass.mtp.entity.SelectValue; import org.iplass.mtp.web.template.TemplateUtil; import samples.ec01.bean.InquiryBean; import samples.ec01.bean.validator.group.JapaneseChecks; import samples.ec01.utils.MapperSelector; // 入力チェック def inquiryBean = new InquiryBean(); request.inquiryBean = inquiryBean; // 日本語専用"name_kana"取得フォーム if (TemplateUtil.getLanguage() == null || TemplateUtil.getLanguage() == "ja") { MapperSelector.select("inquiryBean").populate(inquiryBean, request.getParamMap(), Default.class, JapaneseChecks.class); (1) } else { MapperSelector.select("inquiryBean").populate(inquiryBean, request.getParamMap(), Default.class); (2) } def inquiry = inquiryBean.toEntity(); // 問い合わせステータス // 1 : 未対応 // 2 : 対応中 // 3 : 対応完了 // 4 : 終了 SelectValue inquiryStatus = new SelectValue("1"); inquiry.inquiryStatus = inquiryStatus; // 請求の登録 em.insert(inquiry); "SUCCESS";1 多言語利用で「日本語」が選択されているまたは多言語利用の設定が取得できなかった場合、 samples.ec01.bean.validator.group.JapaneseChecksグループとデフォルトグループに属する項目に対してバリデーションが実行されます。
※ populate(Object, Map, Class)メソッドはスレッドセーフですが、それ以外の delimiters(char, char, char)等の設定用メソッドがスレッドセーフではありません。 詳しい説明はorg.iplass.mtp.command.beanmapper.BeanParamMapperのJavaDocを参照してください。2 多言語利用で「日本語」以外が選択されている場合、デフォルトグループに属する項目のみに対してバリデーションが実行されます。  - 
GroovyTemplateファイル

Template名
samples/ec01/inquiry/registInquiry
<% bind("bean" : inquiryBean) { %> (1) <div class="form-group row"> ...... <div class="col-12 col-md-6 mt-3"> <div> <% bind("prop" : "familyNameKana") { %> (2) <label for="${name}" class="col-form-label label-hidden">${msg("samples/ec01/general", "samples.ec01.inquiry.regist.familyNameKana")}</label> <input type="text" class="form-control border rounded input-hint-visible" name="${name}" value="${value}" placeholder="${msg('samples/ec01/general', 'samples.ec01.inquiry.regist.familyNameKana')}"> (3) <small class="form-text text-danger"><% errors() %></small> (4) <% } %> </div> </div> ----------------------------------------以下略----------------------------------------1 org.iplass.mtp.impl.web.template.groovy.BindContextにBeanインスタンスをバインドします。2 org.iplass.mtp.impl.web.template.groovy.BindContextにBeanインスタンスに格納されているプロパティ名と値をバインドします。autoDetectErrors=trueの場合、WebRequestConstants.EXCEPTIONをキーにMappingExceptionを取得し、MappingResultのインスタンスが自動解決されます。当該Bean、プロパティに紐付くエラーがバインドされます。3 バインドされたプロパティの名前と値をテキストボックスにバインドします。 4 バインドされたエラーメッセージを画面に出力します。 ※ 詳しい使い方については、
org.iplass.mtp.impl.web.template.groovy.WebGTmplBaseを参照してください。 - 
動作確認
- 
「姓」と「名」を空文字として登録しようとした場合に、バリデーションエラーが発生することを画面から確認できます。
 - 
「セイ」と「メイ」に全角カタカナ以外の値を入れて登録しようとした場合に、バリデーションエラーが発生することを画面から確認できます。

 - 
多言語利用で「英語」が選択された場合、英語のバリデーションエラーメッセージが表示されることを確認できます。

※ 英語用の画面にカタカナの「セイ」と「メイ」の入力項目がないので、日本語版のものと比べてレイアウトに少し違いがあります。
 
 - 
 
3.4. Entity
- 
Groovy/GroovyTemplateを利用する場合、Java/JSP版のようなMappingClassの作成は不要です。
 - 
Entityの多言語設定をします。
Admin Console部分の設定でEntity多言語対応を参照してください。 
3.5. ErrorUrlSelector
エラー画面Template制御スクリプトです。
- 
トークンエラーが発生した場合を例として説明していきます。
 
Admin Consoleで、テナント情報の設定から「エラー画面Template制御Script」を開きます。
+

+
import org.iplass.mtp.auth.UserExistsException;
import org.iplass.mtp.entity.EntityValidationException;
import org.iplass.mtp.web.WebRequestConstants;
import org.iplass.mtp.web.actionmapping.TokenValidationException;
import samples.ec01.exception.SessionValueNotFoundException;
def error = request."mtp.web.exception";
//ECサイト用
if (path != null && path.startsWith("samples/ec01")) {
    // TokenValidationException用
    if (error instanceof TokenValidationException) { (1)
        return "samples/ec01/error/genericError";
        // EntityValidationException用
    } else if (error instanceof EntityValidationException) {
        return "samples/ec01/error/genericError";
        // UserExistsException
    } else if (error instanceof UserExistsException) {
        return "samples/ec01/error/genericError";
        // SessionValueNotFoundException
    } else if (error instanceof SessionValueNotFoundException) {
        return "samples/ec01/error/genericError";
        // その他のエラー用
    } else {
        return "samples/ec01/error/genericError";
    }
}
| 1 | TokenValidationExceptionが発生した場合、samples/ec01/error/genericError テンプレートを呼び出すように実装します。
  | 
||
| 2 | TokenValidationExceptionが発生した場合、該当するエラーメッセージを表示します。 ※ ${msg()} については、開発者ガイドのGroovyTemplateの関数の章を参照してください。メタデータとして定義されているメッセージ定義を出力します。
  | 
3.6. ReportOutput(注文明細ダウンロード)
- 
POIを用いてExcel形式の注文明細帳票の出力機能を実装しています。
 - 
Groovy版では、GroovyScriptを用いてTemplateの「Output Logic」を実装しています。

 - 
帳票テンプレート
作成方法については、開発者ガイドの帳票出力(Jasper/JXLS/POI)を参照ください。 - 
POI帳票出力機能の動作確認
- 
管理画面で注文検索一覧画面を開き、「詳細」リンクをクリックします。

 - 
「注文情報ダウンロード」ボタンをクリックします。

 - 
ダウンロードしたExcel帳票を確認します。
サンプルアプリでは、英語用の帳票テンプレートも別途用意しています。
 
 - 
 
3.7. ビルトインGroovyTemplate関数
- 
ここではビルトインのGroovyTemplate関数を利用しています。詳細は、開発者ガイドのGroovyTemplateの関数を参照してください。
 
3.8. WebApiとの連携
Ajaxを利用することで、WebApiで連携することができます。一般消費者向けのECサイト画面における全文検索処理を例として説明していきます。
- 
開発者ガイドのWebApiの作成方法を参照してください。
以下はここで利用しているWebApiです。
WebApi名
samples/ec01/search/fulltextSearch
 - 
WebApiを利用しているテンプレート

Template名
samples/ec01/search/search
----------------------------------------GroovyTemplateの部分略---------------------------------------- <script type="text/javascript"> function fullTextSearch() { const productName = \$("#productName").val(); const categoryOid = \$("#categoryList").attr("category-item-selected"); if(productName == "") { \$("#helpId").html("${msg('samples/ec01/general', 'samples.ec01.search.nokeyword')}"); return false; } const param = "{\"productName\":\"" + productName + "\",\"categoryOid\":\"" + categoryOid +"\"}"; \$.ajax({ type: "POST", contentType: "application/json", url:"${tcPath()}/api/samples/ec01/search/fulltextSearch", (1) dataType: "json", data: param, success: function(commandResult){ if (commandResult.exceptionType != null) { (2) alert("${msg('samples/ec01/general', 'samples.ec01.search.jsError')}"+ commandResult.exceptionType +"\\n"+commandResult.exceptionMessage); return; } if(commandResult.status == "SUCCESS"){ if(commandResult.defaultResult != null && commandResult.defaultResult.length > 0){ const resultHtml = ListSearchResult(commandResult.defaultResult, productName); (3) \$("#searchResultDiv").html(resultHtml); } else{ \$("#helpId").html("${msg('samples/ec01/general', 'samples.ec01.search.keyword')}: " + productName + ", " + "${msg('samples/ec01/general', 'samples.ec01.search.noResult')}"); } } } }); } function dropdownSelect(item){ const t = \$(item); const v = t.attr("category-item-value"); \$("#categoryList").text(t.html()).attr("category-item-selected", v); } function ListSearchResult(entities, productName){ (4) const yen = "${msg('samples/ec01/general', 'samples.ec01.all.yen')}"; let html = "<div class=\"col-12 mb-2\">"; html += " <h4>${msg('samples/ec01/general', 'samples.ec01.search.result')}" + productName + "</h4>"; html += "</div>"; for(let i =0; i < entities.length; i++){ const name = entities[i].name; const price = isNaN(entities[i].price)? "" : entities[i].price; const imageUrl = "${tcPath()}/samples/ec01/resource/bin?id=" + entities[i].productImg.lobId + "&type=productImg"; const detailUrl = "${tcPath()}/samples/ec01/product/detail?productId=" + entities[i].oid; html += "<div class=\"col-12 col-md-4\">"; html += " <div class=\"card border-light border-0\">"; html += " <a href=\"" + detailUrl + "\" class=\"h-100\">"; html += " <img class=\"card-img-top img-thumbnail img-fluid all-product-img\" src=" + imageUrl + " alt=\"" + name + "\">"; html += " </a>"; html += " <div class=\"card-body pt-md-1 text-center\">"; html += " <div>"; html += " <a href=\""+ detailUrl +"\" class=\"card-link text-dark\">" + name + "</a>"; html += " </div>"; html += " <div class=\"all-price\">"; html += " <span>" + price + "</span>" + yen; html += " </div>"; html += " </div>"; html += " </div>"; html += "</div>"; } return html; } </script> ----------------------------------------以下略----------------------------------------1 WebApiによる検索処理を呼び出します。 2 検索処理でエラーが発生した場合、クライアント側での処理。 3 返された検索処理の結果を取得し、クライアント側の描画処理を呼び出します。 4 検索結果の描画処理。  - 
動作確認
動作結果はJava/JSP版と同じなので、そちらの動作確認を参照してください。
 
3.9. セキュリティ対策
オレンジ色の部分を例にセキュリティ対策を解説していきます。
ここでは会員登録を例としてiPLAssで利用できる以下のセキュリティ対策について説明します。
・ XSS対策となるエスケープ機能 : ユーザーの入力内容を正常に画面表示させる
・ CSRF対策/トランザクション重複起動対策となるトークンチェック機能 : 画面表示時に正常な画面遷移が行われているかをチェックする
会員登録処理の中でそれぞれの機能は下記のように利用されます。
- 
$エスケープの導入
 
入力された値を出力する会員情報確認画面にて、該当箇所に$エスケープを導入します。
| 表示名 | Template名 | 
|---|---|
会員情報確認画面  | 
samples/ec01/member/registConfirm  | 
----------------------------------------以上略----------------------------------------
    <div class="card col-12 bg-light">
        <div class="card-body">
            <div class="row mt-3 border-bottom">
                <div class="col-12 col-md-4">
                    <span class="text-muted fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.userId")}</span>
                </div>
                <div class="col-12 col-md-8">$h{userBean.userId}</div> (1)
            </div>
            <div class="row mt-3 border-bottom">
                <div class="col-12 col-md-4">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.registConfirm.fullName")}</span>
                </div>
                <div class="col-12 col-md-3">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.familyName")}</span>
                     $h{userBean.familyName} (1)
                </div>
                <div class="col-12 col-md-3">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.firstName")}</span>
                     $h{userBean.firstName} (1)
                </div>
            </div>
            <div class="row mt-3 border-bottom">
                <div class="col-12 col-md-4">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.registConfirm.fullNameKana")}</span>
                </div>
                <div class="col-12 col-md-3">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.familyNameKana")}</span>
                     $h{userBean.familyNameKana} (1)
                </div>
                <div class="col-12 col-md-3">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.firstNameKana")}</span>
                     $h{userBean.firstNameKana} (1)
                </div>
            </div>
            <div class="row mt-3 border-bottom">
                <div class="col-12 col-md-4">
                    <span class="text-muted  fw-bold">${msg("samples/ec01/general", "samples.ec01.member.regist.mail")}</span>
                </div>
                <div class="col-12 col-md-8">$h{userBean.mail}</div>
            </div>
        </div>
    </div>
----------------------------------------以下略----------------------------------------
| 1 | 今回は出力先がHTML形式であるためすべて $h{変数名} の形式でエスケープを行っています。詳細は、開発者ガイドの記述形式の章を参照してください。
  | 
||||||||||||||||
| 2 | GroovyTemplate関数を利用してトークンを生成しています。 | 
3.10. 多言語対応テンプレート
- 
言語別に同じURLで異なるレイアウトを表示させることが可能です。
日本語用お問合せ登録画面

英語用お問合せ登録画面

 - 
多言語用テンプレートの作成
テンプレート画面の「MultilingualAttribute」を開き、「Add」ボタンより日本語用・英語用テンプレートを作成します。
※ 指定した言語のテンプレートが存在しないときには、あらかじめ作成されているテンプレートが表示されます。
(Templateの
samples/01/inquiry/registInquiryを参照)

- 
カタカナ「セイ」の入力項目を日本語用画面に表示させます。
 - 
カタカナ「メイ」の入力項目を日本語用画面に表示させます。
 
 - 
 
4. リソース定義
4.1. 多言語メッセージ
- 
GroovyTemplateで特定のメッセージ定義(メタデータ)、またはResourceBundleに定義された文字列を取得することができ、言語別にメッセージ定義を作成することで多言語利用が可能です。
取得方法については、開発者ガイドのGroovyTemplateの関数の${rs()}と${msg()}関数を参照してください。
サンプルアプリでは、メタデータとして定義しています。
Message名
samples/ec01/general

 
4.2. Bean Validationエラーメッセージ
- 
言語別にメッセージ定義(メタデータ)を作成することで多言語利用が可能です。

Message名
ValidationMessages

- 
メッセージにパラメータを利用することが可能です。
 
 - 
 - 
補足
※ サンプルアプリでは、Validationエラーメッセージの定義を/messageの直下に入れています。もしカスタムのパスに入れたい場合、以下のようにservice-configにorg.iplass.mtp.impl.validation.ValidationServiceの定義を上書きする必要があります。<service> <interfaceName>org.iplass.mtp.impl.validation.ValidationService</interfaceName> <className>org.iplass.mtp.impl.validation.ValidationService</className> <property name="beanValidation"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> <property name="messageInterpolator" className="org.iplass.mtp.impl.validation.bean.TenantContextMessageInterpolator"> <property name="messageInterpolatorFactory" className="org.iplass.mtp.impl.validation.bean.hibernate.HibernateMessageInterpolatorFactory"> <property name="resourceBundleLocator" className="org.iplass.mtp.impl.validation.bean.hibernate.MessageResourceBundleLocator"> <property name="bundleName" value="[カスタムのパス]/ValidationMessages" /> (1) </property> <property name="cachingEnabled" null="true" /> </property> </property> </property> </service>1 Validationエラーメッセージ定義のパスを[カスタムのパス]/ValidationMessagesに変更します。  
4.3. StaticResource
- 
サンプルアプリでは、JavaScript、CSS、FontファイルをZip形式の圧縮ファイルにしてメタデータとして登録しています。

StaticResource名
samples/ec01/resources

resources.zipファイルを解凍したディレクトリ構造は以下の通りです。
resources ┣ scripts (1) ┗ styles (2) ┗ fonts (3)1 JavaScriptリソースのフォルダ 2 CSSリソースのフォルダ 3 Fontリソースのフォルダ  - 
Actionの作成
上記の圧縮ファイルにエントリされたリソースファイルを指定するActionを作成します。

Action名
samples/ec01/resource/ref
 - 
GroovyTemplateでアクセスする例
<%@ page import="org.iplass.mtp.web.template.TemplateUtil"%> <!DOCTYPE html> <html lang="<%=TemplateUtil.getLanguage() %>"> <% def totalAmount = request.totalAmount; %> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Optional meta tags --> <meta name="keywords" content="キーワードその1,キーワードその2,キーワードその3,キーワードその4,キーワードその5" /> <meta name="description" content="ご自身のWebサイトの説明を記載します" /> <!-- Bootstrap CSS --> <link rel="stylesheet" href="${tcPath()}/samples/ec01/resource/ref/styles/bootstrap.min.css?cv=<%=TemplateUtil.getAPIVersion()%>"> (1) <link rel="stylesheet" href="${tcPath()}/samples/ec01/resource/ref/styles/open-iconic-bootstrap.min.css?cv=<%=TemplateUtil.getAPIVersion()%>"> (1) <link rel="stylesheet" href="${tcPath()}/samples/ec01/resource/ref/styles/bookstore.css?cv=<%=TemplateUtil.getAPIVersion()%>"> (1) </head> ----------------------------------------以下略----------------------------------------1 {コンテキストルート}/{テナント名}/resource/ref/のURLパスで、リソースファイルを取得します。※ 詳しくは、開発者ガイドのStatic Resourceの章を参照してください。
 





