4.0
Groovy/GroovyTemplate版

1. 概要

Groovy/GroovyTemplateを利用して実装されたサンプルアプリケーションです。

2. セットアップ

サンプルアプリケーションのセットアップに関する説明です。
もし、既にiPLAssのInstall and Runまたは開発環境の構築を実行できており、サンプルアプリケーションを動かしたい場合、以下の手順に従って実施してください。
それ以外の場合はInstall and Runから実際に動かしてみることをお勧めします。

  • サンプルアプリケーションのzipファイルをダウンロードサイトからダウンロードします。

  • Install and Runの章で作成したテナント、または開発環境の構築の章でEclipse上で作成したテナント、いずれかを利用してWebアプリケーションを立ち上げ、管理者IDとパスワードでログインします。

サンプルアプリの起動手順
  1. Admin Consoleの Packaging 機能を利用してzipファイルをインポートします。

    ダウンロードした「iplass-sample-app-groovy-package.zip」ファイルを、AdminConsoleの「Packaging」ツールで取り込んでください。

    sample ec groovy gtmpl setup package upload

    デフォルトの設定で「import」ボタンを押下してください。

    sample ec groovy gtmpl setup package import

    「iplass-sample-app-groovy-package.zip」ファイルにテナント情報が含まれているため、「import」ボタン押下後に以下の画面が表示されます。 チェックがついている項目は上書きされてしまうため、独自に設定した項目についてはチェックを外してimportしてください。

    sample ec groovy gtmpl setup package import tenantinfo

    Importを実行すると「Log」パネルに処理状況が表示されます。処理が終了したタイミングでエラーが発生していないことを確認してください。

    sample ec groovy gtmpl setup package import logpanel

    新たに作成したロールなどを反映させるために、メタデータのキャッシュを削除してください。

    sample ec groovy gtmpl setup package import refresh
  2. サンプルアプリでは、全文検索機能(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.xml

    EclipseからWebアプリケーションを起動した場合、プロジェクトフォルダにある設定ファイルを更新してください。

    src/main/resources/mtp-service-config.xml
  3. Admin Consoleの EntityExplorerEntity Crawl 機能を利用して全文検索のINDEXファイルを作り直します。

    ToolsのEntityExplorerを選択します。

    sample ec groovy gtmpl setup entityexplorer

    全文検索を利用する設定になっている場合、「Entity Crawl」タブが表示されます。

    sample ec groovy gtmpl setup entityexplorer tabs

    A) 任意のEntityのみを対象としてクローリングしたい場合は、リストの対象Entityにチェックを入れて、「Start Crawl」ボタンをクリックしてください。

    B) クローリング対象Entityをすべてクローリングしたい場合は「Re Crawl All Entity」ボタンをクリックしてください。

    C) クローリングが完了したら、最新のINDEXデータを反映させるために、「Refresh」ボタンをクリックしてください。

    sample ec groovy gtmpl setup entityexplorer crawl
  4. 上記起動手順の実施が完了しましたら、サンプルアプリのグローバル設定を確認してください。

3. 機能

3.1. Top画面の作成

  • レイアウトの利用
    Top画面の構成は下図のようになっています。画面共通で利用する2つのレイアウト用テンプレート defaultLayoutshippingLayout を用意し、画面のメインエリアは機能に併せて呼びだす形にしています。ここでは画面のメインエリアに top テンプレートを利用しています。

    sample ec groovy gtmpl template layout

    レイアウト用テンプレート

    sample ec groovy gtmpl defaultlayout template

    1. レイアウト用テンプレート側で、実際のコンテンツを表示したい箇所に

      <%renderContent(); %>

      を記述します。

  • レイアウトアクション

    sample ec groovy gtmpl defaultlayout action

    1. レイアウトテンプレートは画面パーツとしての利用になるため、 parts. not direct access にチェックをいれます。

    2. privilege execute にチェックをいれて特権モードにすると、登録していないユーザーにも公開できます。

  • Top画面アクション

    sample ec groovy gtmpl top action

    sample ec groovy gtmpl top layout action

    1. Top画面のテンプレートでは、レイアウトアクションに defaultLayout を指定しています。

3.2. Utility Class

ここでは、Admin Consoleで作成できる UtilityClass の利用方法を解説します。
sample ec groovy gtmpl utilityclass filetree

  • CartBean クラスを例として説明していきます。 CartBean はオレンジ色の部分で利用されています。

    sample ec groovy gtmpl utilityclass cartbean usage

    ※ワークフロー起動は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の利用方法を説明します。

      カートに入れるコマンド

      sample ec groovy gtmpl utilityclass inputcartinfocommand

      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 を使用しています。

      sample ec groovy gtmpl utilityclass cartbean webapi

3.3. Bean/Bean Validation

iPLAssのBean/Bean Validation機能を利用する場合、バリデーションチェックしたいBeanクラスのプロパティにアノテーションをつけることで、バリデーションが自動で実行されます。また、バリデーション結果を画面に表示できます。

サンプルアプリの問い合わせ登録画面で入力された値に対してバリデーションが実行される機能を例として説明していきます。

  • 必要なUtilityClassを作成します。
    Admin ConsoleのUtilityClassの設定を開きます。

    sample ec groovy gtmpl bean validation classes

    1. カスタムのバリデーショングループ。

    2. JavaBeanクラス。※ このサンプルではJavaライブラリに既存のバリデーションとカスタムのバリデーショングループを利用しています。

    3. 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を利用する

    sample ec groovy gtmpl bean validation registinquirycommand

    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ファイル

    sample ec groovy gtmpl bean validation registinquiry

    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 を参照してください。

  • 動作確認

    • 「姓」と「名」を空文字として登録しようとした場合に、バリデーションエラーが発生することを画面から確認できます。

    • 「セイ」と「メイ」に全角カタカナ以外の値を入れて登録しようとした場合に、バリデーションエラーが発生することを画面から確認できます。

      sample ec groovy gtmpl bean validation error

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

      sample ec groovy gtmpl bean validation error en

      ※ 英語用の画面にカタカナの「セイ」と「メイ」の入力項目がないので、日本語版のものと比べてレイアウトに少し違いがあります。

3.4. Entity

  • Groovy/GroovyTemplateを利用する場合、Java/JSP版のようなMappingClassの作成は不要です。

  • Entityの多言語設定をします。
    Admin Console部分の設定でEntity多言語対応を参照してください。

3.5. ErrorUrlSelector

エラー画面Template制御スクリプトです。

  • トークンエラーが発生した場合を例として説明していきます。

Admin Consoleで、テナント情報の設定から「エラー画面Template制御Script」を開きます。

+ sample ec groovy gtmpl tenant errorselector

+

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 テンプレートを呼び出すように実装します。
  • 共通エラー画面

    sample ec groovy gtmpl tenant errorselector genericerror

    Template名

    samples/ec01/error/genericError

    ----------------------------------------以上略----------------------------------------
    <div class="row">
        <div class="col-12">
            <div class="border-top"></div>
            <nav class="breadcrumb all-breadcrumb">
                <a class="breadcrumb-item text-primary" href="${tcPath()}/samples/ec01/top">${msg("samples/ec01/general", "samples.ec01.all.breadcrumb.home")}</a>
                <span class="breadcrumb-item active" >${msg("samples/ec01/general", "samples.ec01.error.title")}</span>
            </nav>
        </div>
        <div class="col-12">
            <span class="h4">${msg("samples/ec01/general", "samples.ec01.error.title")}</span>
            <div class="mt-3">
                <div class="alert alert-danger" role="alert">
                    <p class="mb-0">
                        <strong>
                    <%  Exception e = (Exception) request."mtp.auth.error";
                        if (e == null) {
                            e = (Exception) request."mtp.web.exception";
                        }
                        if (e instanceof LoginFailedException){ %>
                            ${msg("samples/ec01/general", "samples.ec01.error.loginFailed.msg")}
                        <% } else if (e instanceof UserExistsException) { %>
                            ${msg("samples/ec01/general", "samples.ec01.error.userExists.msg")}
                        <% } else if (e instanceof TokenValidationException) { %>
                            ${msg("samples/ec01/general", "samples.ec01.error.token.msg")} (1)
                        <% } else if (e instanceof EntityValidationException) { %>
                            ${msg("samples/ec01/general", "samples.ec01.error.entityValidation.msg")}
                        <% } else if (e instanceof SessionValueNotFoundException) { %>
                            ${msg("samples/ec01/general", "samples.ec01.error.sessionValueNotFound.msg")}
                        <% } else { %>
                            ${msg("samples/ec01/general", "samples.ec01.error.system.msg")}
                        <% } %>
                        </strong>
                    </p>
                </div>
            </div>
            <div class="col-12 mt-5 text-center">
                <a class="btn btn-dark" href="${tcPath()}/samples/ec01/top">${msg("samples/ec01/general", "samples.ec01.error.return")}</a>
            </div>
        </div>
    </div>
2 TokenValidationExceptionが発生した場合、該当するエラーメッセージを表示します。
${msg()} については、開発者ガイドのGroovyTemplateの関数の章を参照してください。メタデータとして定義されているメッセージ定義を出力します。
  • 動作確認

    コンソールに出力されたエラーログは以下の通りです。

    14:48:55.550 [http-nio-8080-exec-14] DEBUG 26 907 samples/ec01/shipping/ConfirmShippingInfoCommand  o.i.m.i.transaction.LocalTransaction - create new Transaction:org.iplass.mtp.impl.transaction.LocalTransaction@36460089 with readOnly=false, stacked:null
    14:48:55.555 [http-nio-8080-exec-14] DEBUG 26 907 samples/ec01/shipping/ConfirmShippingInfoCommand  o.iplass.mtp.transaction.Transaction - rollback transaction cause org.iplass.mtp.web.actionmapping.TokenValidationException: 不正な画面遷移が発生しました(一連の登録処理中にブラウザの戻るボタン等を押下してしまいますと正常に処理を継続できない場合があります)。:org.iplass.mtp.impl.transaction.LocalTransaction@36460089
    org.iplass.mtp.web.actionmapping.TokenValidationException: 不正な画面遷移が発生しました(一連の登録処理中にブラウザの戻るボタン等を押下してしまいますと正常に処理を継続できない場合があります)。
        at org.iplass.mtp.impl.web.interceptors.TokenInterceptor.doError(TokenInterceptor.java:84)
        at org.iplass.mtp.impl.web.interceptors.TokenInterceptor.intercept(TokenInterceptor.java:114)
        at org.iplass.mtp.impl.command.InvocationImpl.proceedCommand(InvocationImpl.java:115)
        at org.iplass.mtp.impl.web.actionmapping.WebInvocationImpl.proceedCommand(WebInvocationImpl.java:171)
        at org.iplass.mtp.impl.command.interceptors.TransactionInterceptor.lambda$intercept$0(TransactionInterceptor.java:34)
        at org.iplass.mtp.transaction.TransactionManager.doTransaction(TransactionManager.java:114)
        at org.iplass.mtp.transaction.Transaction.with(Transaction.java:303)
        at org.iplass.mtp.impl.command.interceptors.TransactionInterceptor.intercept(TransactionInterceptor.java:33)
        at org.iplass.mtp.impl.command.InvocationImpl.proceedCommand(InvocationImpl.java:115)
        at org.iplass.mtp.impl.web.actionmapping.WebInvocationImpl.proceedCommand(WebInvocationImpl.java:171)
    ----------------------------------------以下略----------------------------------------

    sample ec groovy gtmpl tenant errorselector token error

3.6. ReportOutput(注文明細ダウンロード)

  • POIを用いてExcel形式の注文明細帳票の出力機能を実装しています。

  • Groovy版では、GroovyScriptを用いてTemplateの「Output Logic」を実装しています。

    sample ec groovy gtmpl report template order

  • 帳票テンプレート
    作成方法については、開発者ガイドの帳票出力(Jasper/JXLS/POI)を参照ください。

  • POI帳票出力機能の動作確認

    • 管理画面で注文検索一覧画面を開き、「詳細」リンクをクリックします。

      sample ec groovy gtmpl report order list

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

      sample ec groovy gtmpl report download

    • ダウンロードしたExcel帳票を確認します。
      サンプルアプリでは、英語用の帳票テンプレートも別途用意しています。

      sample ec groovy gtmpl report download file

3.7. ビルトインGroovyTemplate関数

  • ここではビルトインのGroovyTemplate関数を利用しています。詳細は、開発者ガイドのGroovyTemplateの関数を参照してください。

3.8. WebApiとの連携

Ajaxを利用することで、WebApiで連携することができます。一般消費者向けのECサイト画面における全文検索処理を例として説明していきます。

  • 開発者ガイドのWebApiの作成方法を参照してください。
    以下はここで利用しているWebApiです。

    sample ec groovy gtmpl tenant webapi fulltextsearch

    WebApi名

    samples/ec01/search/fulltextSearch

  • WebApiを利用しているテンプレート

    sample ec groovy gtmpl tenant webapi search

    Template名

    samples/ec01/search/search

    ----------------------------------------GroovyTemplateの部分略----------------------------------------
    <script type="text/javascript">
    function fullTextSearch() {
        var productName = \$("#productName").val();
        var categoryOid = \$("#categoryList").attr("category-item-selected");
        if(productName == "") {
            \$("#helpId").html("${msg('samples/ec01/general', 'samples.ec01.search.nokeyword')}");
            return false;
        }
        var 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){
                        var 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){
        var t = \$(item);
        var v = t.attr("category-item-value");
        \$("#categoryList").text(t.html()).attr("category-item-selected", v);
    }
    function ListSearchResult(entities, productName){ (4)
        var yen = "${msg('samples/ec01/general', 'samples.ec01.all.yen')}";
        var html =  "<div class=\"col-12 mb-2\">";
            html += "   <h4>${msg('samples/ec01/general', 'samples.ec01.search.result')}" + productName + "</h4>";
            html += "</div>";
        for(var i =0; i < entities.length; i++){
            var name = entities[i].name;
            var price = isNaN(entities[i].price)? "" : entities[i].price;
            var imageUrl = "${tcPath()}/samples/ec01/resource/bin?id=" + entities[i].productImg.lobId + "&type=productImg";
            var 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. セキュリティ対策

対象とする機能

オレンジ色の部分を例にセキュリティ対策を解説していきます。

sample ec fullcustomize flow2
セキュリティ対策について

ここでは会員登録を例としてiPLAssで利用できる以下のセキュリティ対策について説明します。

・ XSS対策となるエスケープ機能 : ユーザーの入力内容を正常に画面表示させる
・ CSRF対策/トランザクション重複起動対策となるトークンチェック機能 : 画面表示時に正常な画面遷移が行われているかをチェックする

会員登録処理の中でそれぞれの機能は下記のように利用されます。

  • $エスケープの導入

Templateの作成

入力された値を出力する会員情報確認画面にて、該当箇所に$エスケープを導入します。

sample ec groovy gtmpl security registconfirm
表示名 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>
                    &nbsp;$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>
                    &nbsp;$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>
                    &nbsp;$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>
                    &nbsp;$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{変数名} の形式でエスケープを行っています。詳細は、開発者ガイドの記述形式の章を参照してください。
  • TokenCheckの導入

  • トークンチェックの導入
    この機能では、iPLAssで実装されている正常に画面遷移してきたことを証明するCSRFトークンを利用します。不正な画面遷移を禁止するActionでトークンをチェックすることにより、正常な画面遷移が行われたかを判断します。

    • Actionの設定
      不正な画面遷移を禁止するActionでトークンをチェックする設定を行います。

      sample ec groovy gtmpl security confirmmemberinfo

      表示名 Action名

      会員情報確認アクション

      samples/ec01/member/confirmMemberInfo

      sample ec groovy gtmpl security token check

      Token Check

      トークンチェックを行うかどうか

      use Fixed Token

      セッション単位に固定に払いだされるトークンを利用するか

      consume a Token

      トークンを消費するか

      rollback on exception

      Exception発生時にトークンをロールバックするか

      この設定により直接この画面へ遷移するとエラーページが表示されます。

      sample ec groovy gtmpl security token error

    • Templateでトークンを作成して埋め込む
      不正な画面遷移を禁止するActionへ遷移する画面でトークンを作成して埋め込みます。

      sample ec groovy gtmpl security regist

      表示名 Template名

      会員情報確認画面

      samples/ec01/member/regist

      <div class="row">
          <div class="col-12">
              <div class="border-top"></div>
              <nav class="breadcrumb all-breadcrumb">
                  <a class="breadcrumb-item text-primary" href="${tcPath()}/samples/ec01/top">
                      ${msg("samples/ec01/general", "samples.ec01.all.breadcrumb.home")}
                  </a>
                  <span class="breadcrumb-item active">
                      ${msg("samples/ec01/general", "samples.ec01.member.regist.title")}
                  </span>
              </nav>
          </div>
          <div class="col-12">
              <span class="h4">${msg("samples/ec01/general", "samples.ec01.member.regist.title")}</span>
              <form class="custom-form mt-3" action="${tcPath()}/samples/ec01/member/confirmMemberInfo" method="post">
              <input type="hidden" name="_t" value="${token()}"> (1)
              <% bind("bean" : userBean, "mappingResult" : result) { %>
                  <div class="form-group row">
                      <div class="col-12">
                          <div>
                              <% bind("prop": "userId") { %>
                              <label for="${name}" class="col-form-label label-hidden">${msg("samples/ec01/general", "samples.ec01.member.regist.userId")}</label>
                              <input type="text" class="form-control border rounded input-hint-visible" name="${name}" value="${value}" placeholder="${msg('samples/ec01/general', 'samples.ec01.member.regist.userId')}">
                              <small class="form-text text-danger"><% errors() %></small>
                              <% } %>
                          </div>
                      </div>
      ----------------------------------------以下略----------------------------------------
2 GroovyTemplate関数を利用してトークンを生成しています。

3.10. 多言語対応テンプレート

  • 言語別に同じURLで異なるレイアウトを表示させることが可能です。

    日本語用お問合せ登録画面

    sample ec groovy gtmpl multilang registinquiry ja

    英語用お問合せ登録画面

    sample ec groovy gtmpl multilang registinquiry en

  • 多言語用テンプレートの作成

    テンプレート画面の「MultilingualAttribute」を開き、「Add」ボタンより日本語用・英語用テンプレートを作成します。

    ※ 指定した言語のテンプレートが存在しないときには、あらかじめ作成されているテンプレートが表示されます。

    (Templateの samples/01/inquiry/registInquiry を参照)

    sample ec groovy gtmpl multilang registinquiry template

    sample ec groovy gtmpl multilang registinquiry template compare

    1. カタカナ「セイ」の入力項目を日本語用画面に表示させます。

    2. カタカナ「メイ」の入力項目を日本語用画面に表示させます。

4. リソース定義

4.1. 多言語メッセージ

  • GroovyTemplateで特定のメッセージ定義(メタデータ)、またはResourceBundleに定義された文字列を取得することができ、言語別にメッセージ定義を作成することで多言語利用が可能です。
    取得方法については、開発者ガイドのGroovyTemplateの関数${rs()}${msg()} 関数を参照してください。

    サンプルアプリでは、メタデータとして定義しています。

    sample ec groovy gtmpl multilang messages general

    Message名

    samples/ec01/general

    sample ec groovy gtmpl multilang messages

4.2. Bean Validationエラーメッセージ

  • 言語別にメッセージ定義(メタデータ)を作成することで多言語利用が可能です。

    sample ec groovy gtmpl multilang validationmessages

    Message名

    ValidationMessages

    sample ec groovy gtmpl multilang validation messages

    1. メッセージにパラメータを利用することが可能です。

  • 補足
    ※ サンプルアプリでは、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形式の圧縮ファイルにしてメタデータとして登録しています。

    sample ec groovy gtmpl static resource resources

    StaticResource名

    samples/ec01/resources

    sample ec groovy gtmpl static resource

    resources.zipファイルを解凍したディレクトリ構造は以下の通りです。

    resources
        ┣ scripts (1)
        ┗ styles (2)
            ┗ fonts (3)
    1 JavaScriptリソースのフォルダ
    2 CSSリソースのフォルダ
    3 Fontリソースのフォルダ
  • Actionの作成

    上記の圧縮ファイルにエントリされたリソースファイルを指定するActionを作成します。

    sample ec groovy gtmpl static resource ref

    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の章を参照してください。