テンプレート処理中にエラーが発生しました。
Java method "com.liferay.portal.json.JSONFactoryImpl.createJSONObject(String)" threw an exception when invoked on com.liferay.portal.json.JSONFactoryImpl object "com.liferay.portal.json.JSONFactoryImpl@51577ebb"; see cause exception in the Java stack trace.

----
FTL stack trace ("~" means nesting-related):
	- Failed at: navigationJSONObject = jsonFactoryUti...  [in template "17855804202317#32484267#LEARN-ARTICLE-NAV" at line 4, column 9]
----
1<#assign 
2	groupFriendlyURL = themeDisplay.getScopeGroup().getFriendlyURL() 
3	groupPathFriendlyURLPublic = themeDisplay.getPathFriendlyURLPublic() + groupFriendlyURL 
4	navigationJSONObject = jsonFactoryUtil.createJSONObject(navigation.getData()) 
5	navigationMenuItems = 
6
7			"Analytics Cloud": { 
8				"image": "/documents/d${groupFriendlyURL}/analytics_c-svg", 
9				"title": "Analytics Cloud", 
10				"url": "analytics-cloud" 
11			}, 
12			"Commerce": { 
13				"image": "/documents/d${groupFriendlyURL}/commerce_product-svg", 
14				"title": "Commerce", 
15				"url": "commerce" 
16			}, 
17			"DXP": { 
18				"image": "/documents/d${groupFriendlyURL}/dxp_p-svg", 
19				"title": "DXP / Portal", 
20				"url": "dxp" 
21			}, 
22			"Liferay Cloud": { 
23				"image": "/documents/d${groupFriendlyURL}/dxp_c-svg", 
24				"title": "Liferay Cloud", 
25				"url": "liferay-cloud" 
26			}, 
27			"Reference": { 
28				"image": "/documents/d${groupFriendlyURL}/reference-svg", 
29				"title": "Reference", 
30				"url": "reference" 
31
32
33 
34	breadcrumbJSONArray = navigationJSONObject.getJSONArray("breadcrumb") 
35	childrenJSONArray = navigationJSONObject.getJSONArray("children") 
36	parentJSONObject = navigationJSONObject.getJSONObject("parent") 
37	productJSONObject = breadcrumbJSONArray.getJSONObject(breadcrumbJSONArray.length()-1)!navigationJSONObject.getJSONObject("self") 
38	siblingsJSONArray = navigationJSONObject.getJSONArray("siblings") 
39/> 
40 
41<div class="learn-article-nav"> 
42	<#if productJSONObject?has_content && productJSONObject.getString("title")?has_content && navigationMenuItems[productJSONObject.getString("title")]?has_content && navigationMenuItems[productJSONObject.getString("title")].title?has_content> 
43		<div 
44			class="dropdown learn-article-nav-root learn-dropdown" 
45
46			<div class="learn-article-nav-item"> 
47				<div class="d-flex"> 
48					<div class="learn-article-nav-image"> 
49						<img 
50							class="lexicon-icon lexicon-icon-caret-bottom product-icon" 
51							role="presentation" 
52							src='${navigationMenuItems[productJSONObject.getString("title")].image}' 
53							viewBox="0 0 512 512" 
54						/> 
55					</div> 
56 
57					<span class="learn-article-nav-text">${navigationMenuItems[productJSONObject.getString("title")].title}</span> 
58				</div> 
59 
60				<div id="dropdown-icon"> 
61					<svg 
62						class="lexicon-icon lexicon-icon-caret-bottom" 
63						role="presentation" 
64						viewBox="0 0 512 512" 
65
66						<use xlink:href="/o/admin-theme/images/clay/icons.svg#caret-bottom"></use> 
67					</svg> 
68				</div> 
69			</div> 
70 
71			<ul class="dropdown-menu learn-dropdown-menu"> 
72				<#list navigationMenuItems as key, value> 
73					<li> 
74						<a 
75							class="dropdown-item learn-article-nav-item" 
76							href="/w/${navigationMenuItems[key].url}/index" 
77							tabindex="4" 
78
79							<span class="d-flex"> 
80								<span class="learn-article-nav-image"> 
81									<img 
82										class="lexicon-icon lexicon-icon-caret-bottom product-icon mt-0 mr-2" 
83										role="presentation" 
84										src="${value.image}"height: 25px; margin-left: 5px; max-width: none; width: 25px; 
85										viewBox="0 0 512 512" 
86									/> 
87								</span> 
88								<span class="learn-article-nav-text">${value.title}</span> 
89							</span> 
90 
91							<#if navigationMenuItems[productJSONObject.getString("title")].url == value.url> 
92								<span> 
93									<@clay["icon"] symbol="check" /> 
94								</span> 
95							</#if> 
96						</a> 
97					</li> 
98				</#list> 
99			</ul> 
100		</div> 
101	</#if> 
102 
103	<div class="learn-article-nav-content"> 
104		<#if parentJSONObject?has_content && parentJSONObject.getString("url")?has_content> 
105			<div class="learn-article-nav-item learn-article-nav-parent liferay-nav-item p-2"> 
106				<div class="mr-2"> 
107					<a 
108						href='${parentJSONObject.getString("url")}' 
109
110						<svg 
111							class="lexicon-icon lexicon-icon-angle-left" 
112							role="presentation" 
113							viewBox="0 0 512 512" 
114
115							<use xlink:href="/o/admin-theme/images/clay/icons.svg#angle-left"></use> 
116						</svg> 
117					</a> 
118				</div> 
119 
120				<span>${parentJSONObject.getString("title")}</span> 
121			</div> 
122		</#if> 
123 
124		<#if childrenJSONArray.length() gt 0> 
125			<ul class="m-0 p-2"> 
126				<#list 0..childrenJSONArray.length()-1 as i> 
127					<li class="learn-article-nav-item"> 
128						<a 
129							class='liferay-nav-item ${(navigationJSONObject.getJSONObject("self").url == childrenJSONArray.getJSONObject(i).url)?then("selected", "")}' 
130							href="${childrenJSONArray.getJSONObject(i).url}" 
131
132							<span>${childrenJSONArray.getJSONObject(i).getString("title")}</span> 
133						</a> 
134					</li> 
135				</#list> 
136			</ul> 
137		<#elseif siblingsJSONArray.length() gt 0> 
138			<ul class="m-0 p-2"> 
139				<#list 0..siblingsJSONArray.length()-1 as i> 
140					<li class="learn-article-nav-item"> 
141						<a 
142							class='liferay-nav-item ${(navigationJSONObject.getJSONObject("self").url == siblingsJSONArray.getJSONObject(i).url)?then("selected", "")}' 
143							href="${siblingsJSONArray.getJSONObject(i).url}" 
144
145							<span>${siblingsJSONArray.getJSONObject(i).getString("title")}</span> 
146						</a> 
147					</li> 
148				</#list> 
149			</ul> 
150		</#if> 
151	</div> 
152</div> 

承認ワークフローの追加

applicationStateフィールドを追加した後、Delectable Bonsaiはアプリケーションに加えられた変更をレビューし、承認するためのワークフローを実装したい。 現在、アプリケーションのすべての更新は、選択された状態に関係なく、自動的に承認される。 Delectable Bonsaiはapproveddeniedの状態に追加のビジネスロジックを関連付けるので、ワークフローはapplicationStateフィールドをチェックし、これらの値のいずれかが選択された場合、マネージャのレビューが必要です。 ここでは、Liferay クライアント拡張 を使って、このワークフロープロセスをデプロイし、セットアップします。

クライアント拡張機能の展開

  1. サンプルワークスペースをダウンロードし、解凍します。

    curl -o https://resources.learn.liferay.com/courses/latest/en/application-development/implementing-business-logic/liferay-c6s3.zip
    
    unzip liferay-c6s3.zip
    
  2. liferay-c6s3/liferay-course-workspace/client-extensions` に移動します:

    cd liferay-c6s3/liferay-course-workspace/client-extensions
    

    これには2つのクライアント拡張プロジェクトが含まれます:liferay-course-batchliferay-course-etc-spring-boot` です。

  3. このコマンドを実行してクライアントエクステンションをデプロイし、Liferayインスタンスのコンソールでデプロイを確認します:

    ../gradlew clean deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
    2023-07-20 21:03:07.142 INFO  [Refresh Thread: Equinox Container: 8b6a806c-654e-44f8-8210-75b61d29063c][BundleStartStopLogger:77] STARTED liferaycoursebatch_1.0.0 [1721]
    2023-07-20 21:03:12.408 INFO  [Refresh Thread: Equinox Container: 8b6a806c-654e-44f8-8210-75b61d29063c][BundleStartStopLogger:77] STARTED liferaycourseetcspringboot_1.0.0 [1722]
    
  4. liferay-course-etc-spring-boot`プロジェクトに移動し、Spring Bootアプリケーションを起動します:

    cd liferay-course-etc-spring-boot
    
    ./../../gradlew bootRun
    
  5. アプリケーションが起動したら、http://localhost:58081/readyにアクセスする。 アプリケーションが使用可能であれば、ページには "READY "と表示されます。

これで、Distributor Application オブジェクトのワークフローを有効にすることができます。

コードを調べる

提供される Workflow Action クライアント拡張機能は、これらの操作を実行します:

  1. アプリケーションの状態値を取得し、ワークフローの遷移を決定するために使用する。

  2. トランジションに対する非同期HTTP POSTリクエストを行う。

ワークフロー移行の決定

   @PostMapping
   public ResponseEntity<String> post(
      @AuthenticationPrincipal Jwt jwt, @RequestBody String json) {

      log(jwt, _log, json);

      String transition = "auto-approve";

      JSONObject payload = new JSONObject(json);

      JSONObject entryDTO = payload.getJSONObject("entryDTO");

      JSONObject applicationState = entryDTO.getJSONObject("applicationState");

      String applicationStateKey = applicationState.getString("key");

      if (Objects.equals("approved", applicationStateKey) || Objects.equals("denied", applicationStateKey)) {
         transition = "review";
      }
      ...
   }

WorkflowAction1RestController の post メソッドには、JSON Web Token (JWT) とリクエストボディの 2 つのパラメータがある。 トークンはHTTPコールを認証し、リクエスト・ボディはJSON形式の文字列としてデータを含む。

リクエスト本文をログに記録した後、コードは transition 変数を初期化し、その値を auto-approve に設定する。

そして、JSONObject() コンストラクタと .getJSONObject() メソッドを使って applicationState フィールドの key 値を取り出し、そのキーを applicationStateKey 変数に格納する。

最後に、状態キーが approved または denied の場合に、transition 変数を review に更新する条件ブロックを定義する。

POSTリクエストの作成

      try {
         WebClient.Builder builder = WebClient.builder();

         WebClient webClient = builder.baseUrl(
            lxcDXPServerProtocol + "://" + lxcDXPMainDomain
         ).defaultHeader(
            HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE
         ).defaultHeader(
            HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE
         ).build();

         webClient.post(
         ).uri(
            payload.getString("transitionURL")
         ).bodyValue(
            "{\"transitionName\": \"" + transition + "\"}"
         ).header(
            HttpHeaders.AUTHORIZATION, "Bearer " + jwt.getTokenValue()
         ).exchangeToMono(
            clientResponse -> {
               HttpStatus httpStatus = clientResponse.statusCode();

               if (httpStatus.is2xxSuccessful()) {
                  return clientResponse.bodyToMono(String.class);
               }
               else if (httpStatus.is4xxClientError()) {
                  return Mono.just(httpStatus.getReasonPhrase());
               }

               Mono<WebClientResponseException> mono =
                  clientResponse.createException();

               return mono.flatMap(Mono::error);
            }
         ).doOnNext(
            output -> {
               if (_log.isInfoEnabled()) {
                  _log.info("Output: " + output);
               }
            }
         ).subscribe();
      }

ワークフロー遷移を決定した後、コードは非同期HTTP POSTリクエストに進む。 まず、WebClient を初期化して設定し、ベース URL とヘッダーを決定し、WebClientpost() メソッドを呼び出します。

このPOSTリクエストは、元のレスポンスボディにあった transitionURL 値を uri() に使用する。 次に、bodyValue() を使用して、ワークフロー・タスクの transitionNametransition 変数の値で更新する。 リクエストはJWTを使って認証され、レスポンスのHTTPステータスコードに従ってログに記録される。

ディストリビューター・アプリケーションのワークフローを有効にする

  1. グローバルメニュー](../../images/icon-applications-menu.png))を開き、アプリケーションタブに移動し、[プロセスビルダー](Process Builder)をクリックします。

  2. 機械承認のプロセスが表示されることを確認する

    Confirm the Machine Approver process appears.

    このプロセスはバッチクライアント拡張によって追加され、5つのノードを定義する:Start、Machine Review、Manager Review、Update、および Approved である。

    Machine Reviewノードは、ワークフローアクションクライアント拡張機能を使用して、各アプリケーションの状態フィールドをチェックする。 値がapprovedまたはdeniedの場合、アプリケーションはビジネス開発マネージャーによる承認のためManager Reviewノードに送られます。 そうでない場合は、Machine Reviewノードが自動的にApprovedノードにアプリケーションを誘導する。

    This process includes five nodes: Start, Machine Review, Manager Review, Update, and Approved.

  3. コンフィギュレーション*タブを開きます。

  4. Distributor Application」の「Edit 」をクリックし、「Machine Approver」を選択し、「Save*」をクリックします。

ワークフローのテスト

  1. Distributor Applicationsを開き、エントリーの編集を開始します。

  2. 状態を Under Review に設定し、 Save をクリックする。

  3. エントリーのステータスを確認する。 承認されるべきだ。

    The entry's status should be Approved.

  4. エントリーの状態を Approved に変更する。

  5. エントリーのステータスを確認する。 保留中*であるべきだ。

    The entry's status should be Pending.

  6. 個人メニュー を開き、 通知 に移動し、新しい ワークフロー通知*をクリックします。

    View the workflow notification.

  7. レビュータスクを自分に割り当て、承認する。

    Approve the change.

  8. エントリーのステータスを確認する。 承認されるべきだ。

    The entry's status should be Approved.

承認ワークフローを設定し、申請にはマネージャーのレビューが必要です。

今、Delectable Bonsaiは申請、承認、却下の通知を自動化しなければならない。

次へ 通知テンプレートの追加

関連コンセプト