1. 前提条件
本チュートリアルは以下を前提条件としています。 チュートリアルを進めるにあたり、以下のいずれかを実施し、Webアプリケーションが動作する環境を準備してください。
2. 本チュートリアルについて
iPLAssはAdminConsoleから様々な操作をする事で、簡単にデータ操作を行うアプリケーションを作成できますが、全ての利用ユーザーの要件を満たすわけではありません。 本チュートリアルでは、iPLAssを利用したカスタマイズの説明をしつつ、簡単なサンプルを作っていきます。
Action、WebApi、Command、TemplateはそれぞれJava / Groovy、JSP/ GroovyTemplateで記述可能です。 本チュートリアルでは両方の扱い方を説明していきます。
iPLAssはMVC構造をとっており、それぞれ以下のように成り立っています。
- Model
-
-
Entity
RDBでいうテーブルに相当する定義です。 この定義に対し、データの登録や参照を行っていきます。
-
- View
-
-
Template
画面に表示する内容を管理する定義で、JSP/GroovyTemplateで実装が可能です。 内部では、Controllerからの情報を表示させたり、ロジックを組み込む(Modelにアクセスするなど)ことが可能です。
-
- Controller
-
-
Action
Web画面上の1アクション(例えば、ボタン押下から次の画面表示まで、など)を管理する定義です。 内部では、Actionが呼ばれた場合に呼び出す処理(Command)とその後の表示画面先(TemplateやJSP、URLなど)が設定できます。 GEM画面の遷移時に利用されるURLのパスの多くは、Actionのパスとなっています。 -
WebApi
WebApiを管理する定義です。 WebApiの形式は様々な組み合わせが可能となっています(REST/SOAP、json/XMLなど)。 内部では、WebApiが呼ばれた場合に呼び出す処理(Command)と返却値名が設定できます。 -
Command
処理ロジックを管理する定義で、Java/Groovyで実装が可能です。
-
チュートリアル(ベーシック)で作成したEntityへの一括登録ページを作成します。 画面イメージは以下のような単純入力画面となります。
Entityの登録方法に関しては、Entityの作成とデータ操作を参照下さい。
-
商品カテゴリ
Entity情報 Name Display Name tutorial.product.ProductCategory
商品カテゴリ
追加Property情報 Name Display Name Type Reference parentCategory
親カテゴリ
Reference
tutorial.product.ProductCategory
チュートリアル(ベーシック)では、商品カテゴリに以下のデータを登録しました。 未登録の場合は以下のデータを登録してください。
登録例 大カテゴリ 中カテゴリ 小カテゴリ 書籍
コンピュータ・IT
一般・入門書
プログラミング
新書・文庫・ノベルス
-
商品
Entity情報 Name Display Name tutorial.product.Product
商品
追加Property情報 Name Display Name Type Reference price
価格
Integer
productCategory
商品カテゴリ
Reference
tutorial.product.ProductCategory
3. Groovy/GroovyTemplate
ここではGroovy/GroovyTemplateを利用してカスタマイズを行います。 AdminConsoleを利用したカスタマイズになります。
3.1. 処理フロー
Groovyのみで処理を作成する場合、下記のような流れで処理が実行されます。
iPLAssではActionに実行するコマンドや遷移先の情報がまとまっています。 そのため、まずCommand/Template(個別定義)について説明し、その後Action(定義の紐付け)の説明を行います。
3.2. 初期表示処理
アクセス~画面表示までの実装を行います。 AdminConsole画面から操作を行ってください。
初回画面表示時に、画面へ出力するデータの取得処理を作成します。
AdminConsole画面で Command
を選択し、右クリックメニューから コマンドを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/InputProduct |
DisplayName |
商品一括登録画面表示コマンド(Groovy) |
Type |
Script |
登録後、ツリー上に表示されたCommandメタデータをダブルクリックで開きます。 Editボタンを押下し、表示された画面に下記スクリプトを貼り付けてください。
import org.iplass.mtp.entity.query.Query;
// カテゴリ情報の取得
def categories = em.searchEntity(new Query().select("oid", "name").from("tutorial.product.ProductCategory"));
request.setAttribute("categories", categories);
本チュートリアルでは、画面表示時に商品カテゴリを表示しています。
上記処理を行うことで、EntityManager(スクリプト内の変数em)を利用して取得した商品カテゴリEntityの情報を、 categories
という名前で画面へ連携します。
|
設定したら、 Save
ボタンをクリックして保存してください。
初回画面の表示部分を作成します。
AdminConsole画面で Template
を選択し、右クリックメニューから テンプレートを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/bulkInsert |
DisplayName |
一括インサート(Groovy) |
Type |
GroovyTemplate |
登録後、ツリー上に表示されたTemplateメタデータをダブルクリックで開きます。 Editボタンを押下し、表示された画面に下記スクリプトを貼り付けてください。
下記は、画面レイアウトの入力欄と、Commandで取得した商品カテゴリをSelect部品に表示させるためのコードです。
<%
def categories = request.getAttribute("categories");
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
function button_onclick(action) {
var \$form = \$("#bulkForm");
\$form.attr("action", "${tcPath()}/" + action).submit(); (1)
}
</script>
</head>
<body>
<h2>商品一括登録</h2>
<form id="bulkForm" method="post" action="">
<table>
<thead>
<tr><th>商品名</th><th>カテゴリ</th><th>価格</th></tr>
</thead>
<tbody>
<% for (def i = 0; i < 10; i++) { %>
<tr>
<td><input type="text" name="productName" /></td>
<td>
<select name="productCategory">
<%
for (def e : categories) {
%>
<option value="${e.oid}" >${e.name}</option>
<%}%>
</select>
</td>
<td><input type="text" name="productPrice" /></td>
</tr>
<%}%>
</tbody>
</table>
<input type="button" value="一括登録" onclick="button_onclick('tutorial/product/groovy/insertProduct')" />
</form>
</body>
</html>
1 | ${tcPath()} を指定することで、アプリケーションのコンテキスト名とテナント名を動的に取得でき、環境によってコードを修正するような事態を避けることができます。 |
設定したら、 Save
ボタンをクリックして保存してください。
アクセスされた場合に、どの処理(Command)を呼び出し、どの画面(Template)を表示させるかの紐付け設定を行います。
AdminConsole画面で Action
を選択し、右クリックメニューから アクションを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/inputProduct |
DisplayName |
商品一括登録入力画面(Groovy) |
登録後、ツリー上に表示されたActionメタデータをダブルクリックで開きます。 編集画面内の各項目に下記の内容を設定してください。
項目 | 設定値 | ||||||||
---|---|---|---|---|---|---|---|---|---|
Privilege execute |
チェックあり |
||||||||
Execute Commands |
tutorial/product/groovy/InputProduct |
||||||||
Results |
|
設定したら、 Save
ボタンをクリックして保存してください。
上記設定により、商品一括登録画面表示コマンド(Groovy)の処理を実行後、一括インサート(Groovy)画面を表示するようになります。
3.3. 一括更新処理
一括登録ボタン押下~登録後の表示までの実装を行います。 AdminConsole画面から操作を行ってください。
画面で 一括登録
ボタンが押下された場合に、入力されているデータをEntityへ保存する処理を作成します。
AdminConsole画面で Command
を選択し、右クリックメニューから コマンドを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/InsertProduct |
DisplayName |
商品一括登録コマンド(Groovy) |
Type |
Script |
登録後、ツリー上に表示された Command
メタデータをダブルクリックで開きます。
Editボタンを押下し、表示された画面に下記スクリプトを貼り付けてください。
import org.iplass.mtp.entity.GenericEntity;
import org.iplass.mtp.web.template.TemplateUtil;
def productNames = request.getParams("productName");
def productCategories = request.getParams("productCategory");
def productPrices = request.getParams("productPrice");
for (int i = 0; i < productNames.size(); i++) {
if (productNames[i] != null && productNames[i] != "") {
def entity = new GenericEntity();
entity.setDefinitionName("tutorial.product.Product");
entity.setName(productNames[i]);
def refEntity = new GenericEntity();
refEntity.setValue("oid", productCategories[i]);
entity.setValue("productCategory", refEntity);
entity.setValue("price", productPrices[i]);
em.insert(entity);
}
}
request.setAttribute("dispInput",TemplateUtil.getTenantContextPath() + "/tutorial/product/groovy/inputProduct");
画面で入力された情報を取得して商品名に値があれば、取得時同様にEntityManagerを利用して、1行分のデータを商品Entityに登録する処理を行っています。
チェック処理などを省いているため、例外な値(例えば、値段の入力欄に文字列など)が入力されていた場合は正常に処理されずエラーとなります。
最後に dispInput
という名前に遷移先の情報(ここではアクションの呼び出しを行うパス)を設定します(詳細は後述)。
設定したら、 Save
ボタンをクリックして保存してください。
一括更新処理のチュートリアルでは、登録後完了後は初期表示処理を実行する設定のため、Templateの追加登録はありません。
AdminConsole画面で Action
を選択し、右クリックメニューから アクションを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/insertProduct |
DisplayName |
商品一括登録処理(Groovy) |
登録後、ツリー上に表示されたActionメタデータをダブルクリックで開きます。 編集画面内の各項目に下記の内容を設定してください。
Commandの最後に dispInput
という名前でアクセス情報を設定しました。
Actionの Status Result Action
で以下のように設定すると、アクセス情報先に設定された遷移先へリダイレクトします。
項目 | 設定値 | ||||||||
---|---|---|---|---|---|---|---|---|---|
Privilege execute |
チェックあり |
||||||||
Execute Commands |
tutorial/product/groovy/InsertProduct |
||||||||
Results |
|
設定したら、 Save
ボタンをクリックして保存してください。
上記設定により、商品一括登録画面表示コマンド(Groovy)の処理を実行後、一括インサート(Groovy)画面を表示するようになります。
URLから直接アクセスで確認できます。
アクセス後、 本チュートリアルについて
で示した画面が表示されます。
実際に登録したあとはGEM画面から登録されていることを確認してみて下さい。
http://localhost:8080/コンテキスト名/テナント名/tutorial/product/groovy/inputProduct
3.4. 非同期処理
一括更新処理ではActionを利用して登録を行いました。 次はWebApiを使って非同期処理を行ってみましょう。
Commandは一括更新処理をそのまま利用します。
Templateは初期表示処理で作成したものに処理を追記します。
tutorial/product/groovy/bulkInsert
のTemplateを下記のように書き換えてください。
<%
def categories = request.getAttribute("categories");
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
function button_onclick(action) {
var \$form = \$("#bulkForm");
\$form.attr("action", "${tcPath()}/" + action).submit();
}
function button_onclickAsync(webapi) { (1)
var data = \$("#bulkForm").serialize();
\$.ajax({
url: "${tcPath()}/api/" + webapi,
type: "POST",
dataType: "json",
data: data
}).done((data, textStatus, jqXHR) => {
if (data.exceptionType != null) {
alert("エラーが発生しました。"+ data.exceptionType +"\\n"+data.exceptionMessage);
return;
}
\$("#bulkForm")[0].reset();
}).fail((jqXHR, textStatus, errorThrown) => {
console.log('fail', jqXHR.status);
});
}
</script>
</head>
<body>
<h2>商品一括登録</h2>
<form id="bulkForm" method="post" action="">
<table>
<thead>
<tr><th>商品名</th><th>カテゴリ</th><th>価格</th></tr>
</thead>
<tbody>
<% for (def i = 0; i < 10; i++) { %>
<tr>
<td><input type="text" name="productName" /></td>
<td>
<select name="productCategory">
<%
for (def e : categories) {
%>
<option value="${e.oid}" >${e.name}</option>
<%}%>
</select>
</td>
<td><input type="text" name="productPrice" /></td>
</tr>
<%}%>
</tbody>
</table>
<input type="button" value="一括登録" onclick="button_onclick('tutorial/product/groovy/insertProduct')" />
<input type="button" value="一括登録(非同期)" onclick="button_onclickAsync('tutorial/product/groovy/insertProduct')" /> (2)
</form>
</body>
</html>
1 | WebApiを呼び出すための非同期処理を追記します。 |
2 | Javascriptを呼び出すためのボタンを追記します。 |
AdminConsole画面で WebApi
を選択し、右クリックメニューから WebApiを作成する
を選択します。
表示されたダイアログに下記の内容を設定し、 Save
ボタンをクリックします。
項目 | 設定値 |
---|---|
Name |
tutorial/product/groovy/insertProduct |
DisplayName |
商品一括登録処理(Groovy) |
登録後、ツリー上に表示されたWebApiメタデータをダブルクリックで開きます。 編集画面内の各項目に下記の内容を設定してください。
項目 | 設定値 |
---|---|
Privilege exute |
チェックあり |
Request Type |
REST FORM |
Excute Commands |
tutorial/product/groovy/InsertProduct |
設定したら、 Save
ボタンをクリックして保存してください。
一括登録用の画面を再表示し、追加したボタンから登録してみてください。 先ほどは画面の再読み込みが行われましたが、今回は再読み込みは行われずに登録処理が呼ばれました。
4. Java/JSP
ここではJava/JSPを利用したカスタマイズを行います。 Eclipseを利用したカスタマイズになります。
4.1. 処理フロー
Java/JSPのみで処理を作成する場合、下記のような流れで処理が実行されます。
下図中の <XML / JSP>
<アノテーション>
<アノテーション / Java>
は、実現するための方法/手段を表しています。
4.2. 初期表示処理
アクセス~画面表示までの実装を行います。 Eclipse上で操作を行ってください。
初回画面表示時に、画面へ出力するデータの取得処理(Command)を作成します。 また、実行するコマンドや遷移先の情報の紐付け定義(Action)も同時に設定します。 Eclipse上で以下のようにJavaファイルを作成してください。
パッケージ |
org.iplass.tutorial.product |
クラス名 |
InputProduct |
package org.iplass.tutorial.product;
import org.iplass.gem.command.Constants;
import org.iplass.mtp.ManagerLocator;
import org.iplass.mtp.command.Command;
import org.iplass.mtp.command.RequestContext;
import org.iplass.mtp.command.annotation.CommandClass;
import org.iplass.mtp.command.annotation.CommandConfig;
import org.iplass.mtp.command.annotation.action.ActionMapping;
import org.iplass.mtp.command.annotation.action.Result;
import org.iplass.mtp.command.annotation.action.Result.Type;
import org.iplass.mtp.entity.Entity;
import org.iplass.mtp.entity.EntityManager;
import org.iplass.mtp.entity.SearchResult;
import org.iplass.mtp.entity.query.Query;
@ActionMapping(name="tutorial/product/java/inputProduct",
displayName="商品一括登録入力画面(java)",
privileged=true,
result=@Result(type=Type.JSP,
value="/jsp/tutorial/product/bulkInsert.jsp",
templateName="tutorial/product/java/bulkInsert"),
command=@CommandConfig(commandClass=InputProduct.class)
)
@CommandClass(name="tutorial/product/java/inputProduct", displayName="商品一括登録画面表示コマンド(java)")
public class InputProduct implements Command {
@Override
public String execute(RequestContext request) {
EntityManager em = ManagerLocator.manager(EntityManager.class);
// カテゴリ情報の取得
SearchResult<Entity> categories = em.searchEntity(new Query()
.select("oid", "name")
.from("tutorial.product.ProductCategory"));
request.setAttribute("categories", categories);
return Constants.CMD_EXEC_SUCCESS;
}
}
本チュートリアルでは、画面表示時に商品カテゴリを表示しています。
上記処理を行うことで、EntityManagerを利用して取得した商品カテゴリEntityの情報を、 categories
という名前で画面へ連携します。
また、「InputProduct.java」のコード内でEQLを表現するクラス群からクエリーを作成している部分があります。詳しい説明を知りたい方はEQLリファレンスを参照してください。
CommandやActionは、メタデータ定義(iPLAssで管理するための設定)の登録が必要です。
Entityを作成する際にAdminConsole画面を使ってメタデータ定義を作成したように、CommandやActionも同様の操作が必要になります。 ここでは、AdminConsole画面で操作しないかわりにCommandクラスの上部あるアノテーションを記載することで対応しています。 今回利用している定義情報は以下の通りです。
アノテーション/プロパティ | 内容 | ||
---|---|---|---|
@ActionMapping |
Actionのメタデータ定義:どの処理(Command)を呼び出すか、何処へ遷移させるかの紐付け設定を行います。 |
||
name |
Action内で一意の名前を設定します。 |
||
displayName |
Actionの表示名を設定します。 |
||
privileged |
セキュリティ制約を一切受けずに実行を可能とするかどうかを切り替えます。(trueの場合、iPLAssへログインせずに操作可能な処理となります。) |
||
result |
|
||
@Result |
|||
type |
レスポンス表示方法の種類の指定します。(後ほどJSPを作成するので今回はJSPを指定します。) |
||
value |
遷移先のJSPファイル名を指定します。(後述で定義される名前) |
||
templateName |
type=Type.JSP の場合に、JSPファイルをテンプレートとして扱う際の名前を設定します。 未指定の場合、ファイルパスがテンプレート名になります。 |
||
command |
|
||
@CommandDef |
|||
commandClass |
対象のコマンドクラスを指定します。 |
||
@CommandClass |
Commandのメタデータ定義:Commandクラスをメタデータとして登録します。 |
||
name |
Command内で一意の名前を設定します。 |
||
displayName |
Commandの表示名を設定します。 |
InputProductクラスで設定したアノテーションにより、商品一括登録画面表示コマンド(java)(本クラス)の処理を実行後、一括インサート(java)画面が表示されるようになります。
また、上記の定義をiPLAssが読み込んでくれるように、 src/main/resources
に格納されている mtp-service-config.xml
を編集します。
mtp-service-config.xml
内の MetaDataRepository
の設定に annotatedClass
というプロパティがコメントされています。
<!-- XmlResource MetaData and Annotation MetaData Settings -->
<service>
<interface>org.iplass.mtp.impl.metadata.MetaDataRepository</interface>
<!-- ■ your app metadata xml file name (additional="true) ■ -->
<!--
<property name="resourcePath" value="/xxx-metadata.xml" additional="true" />
-->
<!-- ■ your app command list class (additional="true) ■ -->
<!--
<property name="annotatedClass" value="xxx.command.CommandList" additional="true" />
-->
・・・
</service>
コメントを解除して、作成したCommandクラスを設定してください。
<!-- ■ your app command list class (additional="true) ■ -->
<property name="annotatedClass" value="org.iplass.tutorial.product.InputProduct" additional="true" />
取り込むCommandが複数ある場合、以下のように設定することが可能です。
<!-- ■ your app command list class (additional="true) ■ -->
<property name="annotatedClass" value="org.iplass.tutorial.AaaaCommand" additional="true" />
<property name="annotatedClass" value="org.iplass.tutorial.BbbbCommand" additional="true" />
<property name="annotatedClass" value="org.iplass.tutorial.CcccCommand" additional="true" />
今回のチュートリアルでは数が少ないため、上記のようにCommand処理を記載したクラスを直接指定しています。 ただこの方法のみで登録を行うと、下記のような問題が発生します。
-
Commandの追加(コマンド名の修正)が発生する度に、XMLの修正を行う必要がある
-
Commandの量に伴い、XMLに記載する内容が肥大化する
Commandの登録方法には下記のように纏める事も可能ですので、プロジェクト毎に検討してみてください。
Commandクラスとは別に下記のようなクラスを作成します。
package org.iplass.tutorial;
import org.iplass.mtp.impl.metadata.annotation.MetaDataSeeAlso;
@MetaDataSeeAlso({
org.iplass.tutorial.AaaaCommand.class,
org.iplass.tutorial.BbbbCommand.class,
org.iplass.tutorial.CcccCommand.class,
})
public class CommandList { }
そして、先ほどのmtp-service-config.xmlのvalue値に上記クラスを指定します。
<!-- ■ your app command list class (additional="true) ■ -->
<property name="annotatedClass" value="org.iplass.tutorial.CommandList" additional="true" />
初回画面の表示部分を作成します。
src\main\webapp
配下にJSPファイルを作成してください。
フォルダ(新規作成分) |
/jsp/tutorial/product |
ファイル名 |
bulkInsert.jsp |
<%@ page language="java" pageEncoding="utf-8" trimDirectiveWhitespaces="true"%>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>
<%@ taglib prefix="m" uri="http://iplass.org/tags/mtp"%>
<%@ page import="org.iplass.mtp.entity.GenericEntity"%>
<%@ page import="org.iplass.mtp.entity.Entity"%>
<%@ page import="org.iplass.mtp.entity.SearchResult"%>
<%@ page import="org.iplass.mtp.command.RequestContext"%>
<%@ page import="org.iplass.mtp.web.template.TemplateUtil"%>
<%
RequestContext context = TemplateUtil.getRequestContext();
@SuppressWarnings("unchecked")
SearchResult<Entity> categories = (SearchResult<Entity>) context.getAttribute("categories");
%>
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
function button_onclick(action) {
var $form = $("#bulkForm");
$form.attr("action", "${m:tcPath()}/" + action).submit();
}
</script>
</head>
<body>
<h2>商品一括登録</h2>
<form id="bulkForm" method="post" action="">
<table>
<thead>
<tr>
<th>商品名</th><th>カテゴリ</th><th>価格</th>
</tr>
</thead>
<tbody>
<% for (int i = 0; i < 10; i++) { %>
<tr>
<td><input type="text" name="productName" /></td>
<td>
<select name="productCategory">
<% for (Entity e : categories) { %>
<option value="<c:out value="<%=e.getOid() %>"/>" ><c:out value="<%=e.getName() %>"/></option>
<%}%>
</select>
</td>
<td><input type="text" name="productPrice" /></td>
</tr>
<%}%>
</tbody>
</table>
<input type="button" value="一括登録" onclick="button_onclick('tutorial/product/java/insertProduct')" />
</form>
</body>
</html>
画面レイアウトの定義と、Commandからの情報をSelect部品に表示させる処理を行います。
本来であれば作成したJSPはTemplateとして登録する必要がありますが、先ほど作成したCommandのように、ActionMappingアノテーションで遷移先の設定にJSPを指定した場合、登録を省略できます。
4.3. 一括更新処理
一括登録ボタン押下~登録後の表示までの実装を行います。 Eclipse上で操作を行ってください。
画面で 一括登録
ボタンが押下された場合に、入力されているデータをEntityへ保存する処理(Command)を作成します。
また、実行するコマンドや遷移先の情報の紐付け定義(Action)も同時に設定します。
以下のようにJavaファイルを作成してください。
パッケージ |
org.iplass.tutorial.product |
クラス名 |
InsertProduct |
package org.iplass.tutorial.product;
import org.iplass.mtp.ManagerLocator;
import org.iplass.mtp.command.Command;
import org.iplass.mtp.command.RequestContext;
import org.iplass.mtp.command.annotation.CommandClass;
import org.iplass.mtp.command.annotation.action.ActionMapping;
import org.iplass.mtp.command.annotation.action.Result;
import org.iplass.mtp.command.annotation.action.Result.Type;
import org.iplass.mtp.entity.Entity;
import org.iplass.mtp.entity.EntityManager;
import org.iplass.mtp.entity.GenericEntity;
import org.iplass.mtp.util.StringUtil;
import org.iplass.mtp.web.template.TemplateUtil;
import org.iplass.gem.command.Constants;
@ActionMapping(name="tutorial/product/java/insertProduct",
displayName="商品一括登録処理(java)",
privileged=true,
result=@Result(type=Type.REDIRECT, value="dispInput"))
@CommandClass(name="tutorial/product/java/InsertProduct", displayName="商品一括登録コマンド(java)")
public class InsertProduct implements Command {
@Override
public String execute(RequestContext request) {
EntityManager em = ManagerLocator.manager(EntityManager.class);
String[] productNames = request.getParams("productName");
String[] productCategories = request.getParams("productCategory");
String[] productPrices = request.getParams("productPrice");
for (int i = 0; i < productNames.length; i++) {
if (StringUtil.isNotEmpty(productNames[i])) {
Entity entity = new GenericEntity();
entity.setDefinitionName("tutorial.product.Product");
entity.setName(productNames[i]);
Entity refEntity = new GenericEntity();
refEntity.setValue("oid", productCategories[i]);
entity.setValue("productCategory", refEntity);
entity.setValue("price", productPrices[i]);
em.insert(entity);
}
}
request.setAttribute("dispInput", TemplateUtil.getTenantContextPath() + "/tutorial/product/java/inputProduct");
return Constants.CMD_EXEC_SUCCESS;
}
}
画面で入力された情報を取得して商品名に値があれば、取得時同様にEntityManagerを利用して、1行分のデータを商品Entityに登録する処理を行っています。
チェック処理などを省いているため、例外な値(例えば、値段の入力欄に文字列など)が入力されていた場合は正常に処理されずエラーとなります。
最後に dispInput
という名前に遷移先の情報(ここではアクションの呼び出しを行うパス)を設定しています。
ActionMapping
の result
の設定により、処理終了後は dispInput
という名前に設定されたアクセス情報先へリダイレクトされるようになります。
InsertProductクラスで設定したアノテーションにより、商品一括登録コマンド(Java)の処理を実行後、再度初期表示が実行されるようになります。
このCommandもクラス作成だけでなく、InputProductクラス同様に mtp-service-config.xml
を編集します。
<!-- ■ your app command list class (additional="true) ■ -->
<property name="annotatedClass" value="org.iplass.tutorial.product.InputProduct" additional="true" />
<property name="annotatedClass" value="org.iplass.tutorial.product.InsertProduct" additional="true" />
設定ファイルを追加・更新したため、サーバの再起動が必須になります。 サーバを再起動後、URLから直接アクセスで確認できます。
アクセス後、 本チュートリアルについて
で示した画面が表示されます。
実際に登録したあとはGEM画面から登録されていることを確認してみて下さい。
http://localhost:8080/コンテキスト名/テナント名/tutorial/product/java/inputProduct
4.4. 非同期処理
一括更新処理ではActionを利用して登録を行いました。 次はWebApiを使って非同期処理を行ってみましょう。
WebApiの設定はCommandにアノテーションを設定することで作成します。 一括更新処理のCommandは処理をそのまま利用し、WebApiのアノテーションを追加します。
package org.iplass.tutorial.product;
import org.iplass.mtp.ManagerLocator;
import org.iplass.mtp.command.Command;
import org.iplass.mtp.command.RequestContext;
import org.iplass.mtp.command.annotation.CommandClass;
import org.iplass.mtp.command.annotation.action.ActionMapping;
import org.iplass.mtp.command.annotation.action.Result;
import org.iplass.mtp.command.annotation.action.Result.Type;
import org.iplass.mtp.command.annotation.webapi.RestJson; (1)
import org.iplass.mtp.command.annotation.webapi.WebApi; (1)
import org.iplass.mtp.entity.Entity;
import org.iplass.mtp.entity.EntityManager;
import org.iplass.mtp.entity.GenericEntity;
import org.iplass.mtp.util.StringUtil;
import org.iplass.mtp.web.template.TemplateUtil;
import org.iplass.mtp.webapi.definition.RequestType; (1)
import org.iplass.gem.command.Constants;
@ActionMapping(name="tutorial/product/java/insertProduct",
displayName="商品一括登録処理(java)",
privileged=true,
result=@Result(type=Type.REDIRECT, value="dispInput"))
@WebApi(name="tutorial/product/java/insertProduct", (2)
displayName="商品一括登録処理(java)",
privileged=true,
accepts=RequestType.REST_JSON,
restJson=@RestJson(parameterName="param"))
@CommandClass(name="tutorial/product/java/InsertProduct", displayName="商品一括登録コマンド(java)")
public class InsertProduct implements Command {
@Override
public String execute(RequestContext request) {
EntityManager em = ManagerLocator.manager(EntityManager.class);
String[] productNames = request.getParams("productName");
String[] productCategories = request.getParams("productCategory");
String[] productPrices = request.getParams("productPrice");
for (int i = 0; i < productNames.length; i++) {
if (StringUtil.isNotEmpty(productNames[i])) {
Entity entity = new GenericEntity();
entity.setDefinitionName("tutorial.product.Product");
entity.setName(productNames[i]);
Entity refEntity = new GenericEntity();
refEntity.setValue("oid", productCategories[i]);
entity.setValue("productCategory", refEntity);
entity.setValue("price", productPrices[i]);
em.insert(entity);
}
}
request.setAttribute("dispInput", TemplateUtil.getTenantContextPath() + "/tutorial/product/java/inputProduct");
return Constants.CMD_EXEC_SUCCESS;
}
}
1 | 不足しているアノテーションおよびクラスをインポートしてください。 |
2 | WebApiアノテーションを追記します。 |
Templateは初期表示処理で作成したものに処理を追記します。 作成したTemplateを下記のように書き換えてください。
<%@ page language="java" pageEncoding="utf-8" trimDirectiveWhitespaces="true"%>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>
<%@ taglib prefix="m" uri="http://iplass.org/tags/mtp"%>
<%@ page import="org.iplass.mtp.entity.GenericEntity"%>
<%@ page import="org.iplass.mtp.entity.Entity"%>
<%@ page import="org.iplass.mtp.entity.SearchResult"%>
<%@ page import="org.iplass.mtp.command.RequestContext"%>
<%@ page import="org.iplass.mtp.web.template.TemplateUtil"%>
<%
RequestContext context = TemplateUtil.getRequestContext();
@SuppressWarnings("unchecked")
SearchResult<Entity> categories = (SearchResult<Entity>) context.getAttribute("categories");
%>
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
function button_onclick(action) {
var $form = $("#bulkForm");
$form.attr("action", "${m:tcPath()}/" + action).submit();
}
function button_onclickAsync(webapi) { (1)
var data = "{";
data += "\"productName\":" + JSON.stringify($("[name='productName']").map(function() {return $(this).val()}).toArray());
data += ",\"productCategory\":" + JSON.stringify($("[name='productCategory']").map(function() {return $(this).val()}).toArray());
data += ",\"productPrice\":" + JSON.stringify($("[name='productPrice']").map(function() {return $(this).val()}).toArray());
data += "}";
$.ajax({
url: "${m:tcPath()}/api/" + webapi,
type: "POST",
contentType: "application/json",
dataType: "json",
data: data
}).done((data, textStatus, jqXHR) => {
if (data.exceptionType != null) {
alert("エラーが発生しました。"+ data.exceptionType +"\\n"+data.exceptionMessage);
return;
}
$("#bulkForm")[0].reset();
}).fail((jqXHR, textStatus, errorThrown) => {
console.log('fail', jqXHR.status);
});
}
</script>
</head>
<body>
<h2>商品一括登録</h2>
<form id="bulkForm" method="post" action="">
<table>
<thead>
<tr>
<th>商品名</th><th>カテゴリ</th><th>価格</th>
</tr>
</thead>
<tbody>
<% for (int i = 0; i < 10; i++) { %>
<tr>
<td><input type="text" name="productName" /></td>
<td>
<select name="productCategory">
<% for (Entity e : categories) { %>
<option value="<c:out value="<%=e.getOid() %>"/>" ><c:out value="<%=e.getName() %>"/></option>
<%}%>
</select>
</td>
<td><input type="text" name="productPrice" /></td>
</tr>
<%}%>
</tbody>
</table>
<input type="button" value="一括登録" onclick="button_onclick('tutorial/product/java/insertProduct')" />
<input type="button" value="一括登録(非同期)" onclick="button_onclickAsync('tutorial/product/java/insertProduct')" /> (2)
</form>
</body>
</html>
1 | WebApiを呼び出すための非同期処理を追記します。 |
2 | Javascriptを呼び出すためのボタンを追記します。 |
WebApiを呼び出す非同期処理ですが、Groovy/GroovyTemplateで追記したものと形式が異なることに気がついたでしょうか。
Groovy/GroovyTemplateではWebApiのRequest Typeは REST_FORM
となっていました。
また、ajaxのオプションにcontentTypeが指定されていませんでした。
一方こちらでは、WebApiのRequest Type(アノテーションのaccepts)は REST_JSON
となっています。
ajaxのオプションにはcontentTypeに application/json
を指定しています。
上記のようにWebApiではRequest Typeによって取得するパラメータの形式が変わります。 不適切な形式のパラメータを送信するとエラーになりますので注意してください。
一括登録用の画面を再表示し、追加したボタンから登録してみてください。 先ほどは画面の再読み込みが行われましたが、今回は再読み込みは行われずに登録処理が呼ばれました。
Eclipseで作成したファイルをiPLAssで読み込ませた場合、AdminConsole画面で作成したメタデータとは異なるアイコンで表示されます。
AdminConsole上で作成したメタデータは Local
で、テナント固有のメタデータになります。
一方、アノテーションで定義したメタデータや、iPLAssが提供しているメタデータは Shared
で、Webアプリケーションで定義したメタデータになります。
Shared
の場合、起動したWebアプリケーションからアクセスする全てのテナントで利用可能になります。
また、 Shared
のメタデータをAdminConsoleで編集して保存すると、アイコン下部が赤に変わります。
メタデータの種類が Shared Overwrite
に変わり、テナント固有のメタデータとして扱われるようになります。
Shared Overwrite
から Shared
に戻したい場合は、AdminConsoleで対象のメタデータを右クリックして表示されるコンテキストメニューから削除してください。
5. 次のステップ
承認行為などを行うワークフロー処理を利用したい場合、チュートリアル(ワークフロー)を参照してください。
フルカスタムのデザインを持つコンシューマ向けのWebアプリケーションを構築したい場合、 サンプルアプリケーションの説明 を参照してください。 iPLAss上で動作するECサイトを模したものです。動かして構造を理解してください。
それぞれの機能の詳細を理解したい場合は、 Developer Guide を参照し、いろいろと定義を変更してみてください。