Groovy/GroovyTemplate版

1. 概要

Groovy/GroovyTemplateを利用して実装されたサンプルアプリケーションです。
Groovy/GroovyTemplateのカスタマイズする方法はチュートリアル(カスタマイズ)Groovy/GroovyTemplateをご参照ください。

2. セットアップ

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

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

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

サンプルアプリの起動手順
  1. Admin ConsolePackaging 機能を利用して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)を利用しますので、service-configに書いてある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 ConsoleEntityExplorerEntity Crawl 機能を利用してインデックスファイルを作り直します。

    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」ボタンをクリックして下さい。この場合、チェックをいれていないEntityも全てが対象となります。

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

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

3. 機能

3.1. Top画面の作成

  • Layoutの利用
    Top画面の構成は下図のようになっています。画面共通で利用するレイアウト用テンプレート defaultLayoutshippingLayout を用意し、画面右部分はそれぞれの機能に併せて呼びだす形にしています。ここでは"top"テンプレートを呼び出して利用しています。

    sample ec groovy gtmpl template layout

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

    sample ec groovy gtmpl defaultlayout template

    1. テンプレートを読み込む側(この例だとdefaultLayoutテンプレート)では、読みこんだテンプレートを表示したい箇所に

      <%renderContent(); %>

      を記述します。

  • レイアウトアクション

    sample ec groovy gtmpl defaultlayout action

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

    2. レイアウトテンプレートに関してはパーツとしての利用になる為"parts.not direct access"にチェックをいれます。

  • Top画面アクション

    sample ec groovy gtmpl top action

    sample ec groovy gtmpl top layout action

    1. 読み込まれる側(この例だとTop画面アクション)のテンプレートでレイアウトアクションを defaultLayout のアクションに指定しています。

3.2. Utility Class

ここではAdminConsoleで作成できる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 javax.validation.Valid;
      import javax.validation.constraints.Min;
      import org.hibernate.validator.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を作成しています。
    AdminConsoleの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 javax.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(デフォルトが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制御スクリプトクラスです。

  • Tokenエラーを例として説明していきます。
    Tenantの設定 → エラー画面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>
    1 TokenValidationExceptionが発生した場合、該当するエラーメッセージを表示します。
    ${msg()} は開発者ガイド開発者ガイド GroovyTemplateの関数の章を参照してください。メタデータとして定義されているメッセージ定義を出力します。
  • 動作確認

    Consoleに出力されたエラーログです。

    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. Buildin GroovyTemplate関数

3.8. WebApiとの連携

Ajaxを利用することで、GroovyTemplateでWebApiと連携することが出来ます。一般消費者向け画面で全文検索処理を例として説明していきます。

  • 開発者ガイド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対策 : ユーザーの入力内容を正常に画面表示させる
・ TokenCheck機能  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 font-weight-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  font-weight-bold">${msg("samples/ec01/general", "samples.ec01.member.registConfirm.fullName")}</span>
                    </div>
                    <div class="col-12 col-md-3">
                        <span class="text-muted  font-weight-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  font-weight-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  font-weight-bold">${msg("samples/ec01/general", "samples.ec01.member.registConfirm.fullNameKana")}</span>
                    </div>
                    <div class="col-12 col-md-3">
                        <span class="text-muted  font-weight-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  font-weight-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  font-weight-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で実装されている正常に画面遷移してきたことを証明する"Token"というオブジェクトを利用します。不正な画面遷移を禁止するActionでTokenをチェックすることにより、正常な画面遷移が行われたかを判断します。

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

      sample ec groovy gtmpl security confirmmemberinfo

      表示名 Action名

      会員情報確認アクション

      samples/ec01/member/confirmMemberInfo

      sample ec groovy gtmpl security token check

      Token Check

      Not Check チェックを行わない Check チェックを行う

      use Fixed Token

      チェック→セッション単位に固定に払いだされるTokenをチェックする

      consume a Token

      true→Tokenは再利用されません。

      not change the Token(exception occurs)

      チェック→現在のTokenを再設定

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

      sample ec groovy gtmpl security token error

    • TemplateでTokenを作成
      不正な画面遷移を禁止するActionへ遷移する画面でTokenを作成します。

      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>
      ----------------------------------------以下略----------------------------------------
      1 GroovyTemplate関数を利用してトークンを生成しています。開発者ガイド開発者ガイド GroovyTemplateの関数の章を参照してください。

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

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

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

    sample ec groovy gtmpl multilang registinquiry ja

    英語用お問合せ登録画面

    sample ec groovy gtmpl multilang registinquiry en

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

    テンプレート画面のMutilingualAttributeを開き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をアクセスすることが出来ます。

    ※ 詳しくは開発者ガイドStaticResourceの章を参照してください。