Liferay 6.2のアセットパブリッシャー用アプリケーション・ディスプレイ・テンプレート(ADT)サンプル

本記事は[Liferay 6.2 のWebコンテンツテンプレートを使い倒す](https://qiita.com/yasuflatland-lf/items/9a98f52397869ed1f8b2)とペアになる記事です。

##ADT(アプリケーション・ディスプレイ・テンプレート)とは？
Liferayには`AssetPublisher(アセットパブリッシャー)`という、デフォルトで組み込まれているWebコンテンツやドキュメントとメディア、ブログやWikiといった様々なデータ形式のデータを、様々な方法でフィルタして一覧表示できるポートレットがあります。

ポータルサイトを作成する際には、対象となるデータに絞り込んだり、タグでフィルタを掛けたりして一覧表示が必ず必要になってきますが、その際にまず必ずといっていいほど利用するポートレットになるかと思います。

##Webコンテンツのテンプレートと、ADTの違い
基本的に同じような変数が利用できるのですが、ADTの方が組み込み関数が充実しており、様々な値の取得が容易です。

##推奨テンプレート
[こちら](https://qiita.com/yasuflatland-lf/items/9a98f52397869ed1f8b2#%E6%8E%A8%E5%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88)の記事と同様、バージョン7.1現在、使用可能なテンプレートのフォーマットは`Velocity`と`Freemarker`がありますが、組み込み機能の充実と、表記の柔軟性、また近年のバージョンのアップデート状況を鑑みて、`Freemarker`の利用を推奨します。

##利用可能な全組み込み変数/クラスの出力方法
[こちら](https://qiita.com/yasuflatland-lf/items/9a98f52397869ed1f8b2#%E5%88%A9%E7%94%A8%E5%8F%AF%E8%83%BD%E3%81%AA%E5%85%A8%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E5%A4%89%E6%95%B0%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%87%BA%E5%8A%9B%E6%96%B9%E6%B3%95)と同様の方法で取得可能です。

##組み込みサービスの取得方法
[こちら](https://qiita.com/yasuflatland-lf/items/9a98f52397869ed1f8b2#%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E5%8F%96%E5%BE%97%E6%96%B9%E6%B3%95)と同様の方法で取得可能です。

##よく使う値の取得方法
####Webコンテンツ表示系で絶対使うであろう変数

```text
<#assign
	languageId = languageUtil.getLanguageId(locale)
	tagUtil = serviceLocator.findService("com.liferay.portlet.asset.service.AssetTagLocalService")
	serviceContext = staticUtil["com.liferay.portal.service.ServiceContextThreadLocal"].getServiceContext()
	httpServletRequest = serviceContext.getRequest()
	themeDisplay = serviceContext.getThemeDisplay()
	rootPortletId = portletDisplay.getRootPortletId()
	currentPortletURL = renderResponse.createRenderURL()
/>
```
|変数|説明|
|:--|:--|
|languageId|現在の表示言語から作成した`Locale`オブジェクト|
|tagUtil|`AssetTag`を利用するためのサービス|
|serviceContext|サービスコンテキスト|
|httpServletRequest|リクエスト|
|themeDisplay|テーマディスプレイ|
|rootPortletId|ルートポートレットID。(例えば`"56"`などの文字列)|
|currentPortletURL|現在のページを表示しているURL文字列|

####Webコンテンツデータ取得
Webコンテンツのクラスである`JournalArticle`からデータを取得してくると、データ構造はXMLとなっています。それをパースするのに以下のようなユーティリティ関数を作成する必要があります。

```text
<#function value_of docXml field language_id>
    <#local default_language_id = docXml.valueOf("/root/@default-locale") />
    <#local value = docXml.valueOf("//dynamic-element[@name='" + field + "']/dynamic-content[@language-id='"+ language_id + "']/text()") />
    <#if !value?has_content>
        <#local value = docXml.valueOf("//dynamic-element[@name='" + field + "']/dynamic-content[@language-id='"+ default_language_id + "']/text()") />
    </#if>
    <#return value />
</#function>
```

使い方としては以下のような感じになります。Webコンテンツのストラクチャー構造が

```text
root
├── wcm_title
└── wcm_contents
```

と仮定した場合、(`wcm_title`はタイトル用テキストフィールド)

```text
<#assign
    journalArticle = assetRenderer.getArticle()
    docXml = saxReaderUtil.read(journalArticle.getContent())
    wcm_title = value_of(docXml, "wcm_title", languageId)
>
```
といった形でタイトル部分の文字列が取得できます。

####取得文字列の切り詰め(truncate)
ADTでコンテンツのリスト表示をする際に、よく記事を規定の文字数で切り詰めて、「・・・続きを読む」的な感じの文字列を追加したり、という処理をすることがよくあるかと思います。その処理をするのが以下のユーティリティ関数になります。

```text
<#function truncate field size>
	<#local value = field />
	<#if field?length &lt; size>
		<#local value = field />
	<#else>
		<#local value = field?substring(0,size) + "..." />
	</#if>
	<#return value />
</#function>
```

以下のような使い方をします。

```text
<#assign
    journalArticle = assetRenderer.getArticle()
    docXml = saxReaderUtil.read(journalArticle.getContent())
    wcm_contents = value_of(docXml, "wcm_contents", languageId)
    truncate_length = 28
    truncated_wcm_contents = htmlUtil.escape(truncate(wcm_contents?replace('<[^>]+>','','r'),truncate_length))
>
```
Webコンテンツ内のフィールド`wcm_contents`から文字列を取得し、それを28文字で区切っています。

####Assetデータ処理サンプル

上記の説明を１つのADTにまとめると、以下のようになります。Webコンテンツのストラクチャー構造が

```text
root
├── wcm_title
└── wcm_contents
```

と仮定した場合、

```text
<#assign
	languageId = languageUtil.getLanguageId(locale)
	tagUtil = serviceLocator.findService("com.liferay.portlet.asset.service.AssetTagLocalService")
	serviceContext = staticUtil["com.liferay.portal.service.ServiceContextThreadLocal"].getServiceContext()
	httpServletRequest = serviceContext.getRequest()
	themeDisplay = serviceContext.getThemeDisplay()
	rootPortletId = portletDisplay.getRootPortletId()
	currentPortletURL = renderResponse.createRenderURL()
/>

<#function value_of docXml field language_id>
    <#local default_language_id = docXml.valueOf("/root/@default-locale") />
    <#local value = docXml.valueOf("//dynamic-element[@name='" + field + "']/dynamic-content[@language-id='"+ language_id + "']/text()") />
    <#if !value?has_content>
        <#local value = docXml.valueOf("//dynamic-element[@name='" + field + "']/dynamic-content[@language-id='"+ default_language_id + "']/text()") />
    </#if>
    <#return value />
</#function>

<#function truncate field size>
	<#local value = field />
	<#if field?length &lt; size>
		<#local value = field />
	<#else>
		<#local value = field?substring(0,size) + "..." />
	</#if>
	<#return value />
</#function>

<#if entries?has_content>
    <#list entries as entry>
        <#assign
            assetRenderer = entry.getAssetRenderer()
            className = assetRenderer.getClassName()
            entryTitle = htmlUtil.escape(assetRenderer.getTitle(locale))
            entryId = entry.getEntryId()
            tags = entry.getTags()
            viewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse, entry)
            displayDateFormat = "yyyy.MM.dd"
            tagDateFormat = "yyyy-MM-dd"
            displayDate = dateUtil.getDate(entry.getPublishDate(), displayDateFormat, locale)
            tagDate = dateUtil.getDate(entry.getPublishDate(), tagDateFormat, locale)
        />
            <#-- Only JournalArticle -->
            <#if className == "com.liferay.portlet.journal.model.JournalArticle">
                <#assign
                    journalArticle = assetRenderer.getArticle()
                    docXml = saxReaderUtil.read(journalArticle.getContent())
                    wcm_title = value_of(docXml, "wcm_title", languageId)
                    wcm_contents = value_of(docXml, "wcm_contents", languageId)
                    truncate_length = 28
                    truncated_wcm_contents = htmlUtil.escape(truncate(wcm_contents?replace('<[^>]+>','','r'),truncate_length))
                    author_name = entry.getUserName()
                    tags = tagUtil.getAssetEntryAssetTags(entryId)
                >
                <h2>${wcm_title}</h2>
                <p>${truncated_wcm_contents}</p>
            </#if>
    </#list>
</#if>
```

##自力でのADTサンプルの探し方
Googleで検索は基本なのですが、Github検索が結構役立ちます。Github検索で、キーワードとしては`assetRenderer`や`dynamic-element`などとして、詳細検索画面で拡張子をftlに絞りこんで検索すると、Liferayのテンプレートが色々ヒットします。自分もそれをだいぶ参考にさせてもらいました。

##まとめ
Liferayの公式ドキュメントには基本的な部分のみ載っている感じなので、実際の現場で利用するADTを晒してみました。お役に立てれば幸いです。