ヘッドレスCMS = Watson Contents Hubを使ってみた～ 2.アプリ開発編


当記事は以下の３本の連続記事の３本目です。
1. [ Watson Content Hub = クラウド上のヘッドレスCMS + Watson + CDN ](http://qiita.com/ishida330/items/3593b1cb772d7f3491f9)
2. [ヘッドレスCMS = Watson Contents Hubを使ってみた～ 1.オーサリング編](http://qiita.com/ishida330/items/156350b28b3657b5b413)
3. [ヘッドレスCMS = Watson Contents Hubを使ってみた～ 2.アプリ開発編](http://qiita.com/ishida330/items/7dff30995d48f56d6166)
執筆時点(2017/06)での情報です(WCHは機能追加がどんどん行われているので..)
 
# <i class="fa fa-play" style="font-size:1em;"></i>はじめに
[1.オーサリング編](http://qiita.com/ishida330/items/156350b28b3657b5b413)ではWCHでのコンテンツの準備の仕方についてご紹介しました。当記事では[1.オーサリング編](http://qiita.com/ishida330/items/156350b28b3657b5b413)で定義した内容を使って、プレゼンテーション層に相当するアプリケーション開発についてご紹介します。(下図右側)
![image](https://qiita-image-store.s3.amazonaws.com/0/108535/d6dff063-4b82-1a96-d6f3-272360696bd5.png)

# <i class="fa fa-book" style="font-size:1em;"></i>まずはリソースのご紹介

WCHの開発者用ドキュメントはKnowledge Centerとは別の場所にあります。本格的に取り組まれるなら、まずは下記の:star: [デベロッパー向けドキュメント](https://developer.ibm.com/customer-engagement/docs/wch/)に目を通されることをお勧めします

- [デベロッパー向けサイト](https://developer.ibm.com/customer-engagement/watson-content-hub/)
- :star:[デベロッパー向けドキュメント](https://developer.ibm.com/customer-engagement/docs/wch/)
- [Live Samples Gallery](http://samples.watsoncontenthub.io/sample-pages/) かっこいい実働サンプルが多数掲載されています。ソースもあります。
- [Github](https://github.com/ibm-wch)-サンプルのソースやツールなど
- [API Reference](https://developer.ibm.com/api/view/id-618:title-IBM_Watson_Content_Hub_API#doc) - Swaggerっぽい動かせるリファレンス
- [dW Answers(forum)](https://developer.ibm.com/answers/smart-spaces/301/watson-content-hub.html)　フォーラムで質問できます(英語ですが）

# <i class="fa fa-cube" style="font-size:1em;"></i>おさらい

オーサリング編でも少し触れましたが、WCHのアプリって、**要は「REST APIで取ってきたJSONをパースして、そのデータを使って好きなUIフレームワークでUIを組み立てればいいだけ**」です。UIフレームワークは何を使おうが自由です。以上。:white_check_mark:
![image](https://qiita-image-store.s3.amazonaws.com/0/108535/e1f3ff46-1c6e-a45e-eab0-90baadad52c2.png)

# <i class="fa fa-book" style="font-size:1em;"></i>APIの前提知識

っても、それだけでは身もふたもないので、「やってみる」前にAPIを利用する際の前提知識をご説明しますね。

### オーサリングAPIとデリバリーAPI

WCHのAPIを大別するとオーサリングを行うための**①オーサリングAPI**と本番アプリケーションが使う**②デリバリーAPI**があります。（「ログイン」など共通的なAPIも若干ありますが）

**①オーサリングAPI**は様々なオーサリング作業をプログラミングにより自動化するのが主目的で、オーサリングの画面もオーサリングAPIを使って作られています。オーサリングAPIを使う場合は認証が必要です。
**②デリバリーAPI**はお客様のアプリケーションがWCHへコンテンツを要求する際に使うもので、認証は不要です。（実際のコンテンツはCDN経由で配付されます）

オーサリングAPIについて若干補足します

- オーサリングUIやコマンドツール( wchtools )など製品自身が利用しています
- 基本的なオーサリングはUIやコマンドツールで作業できるため、オーサリングAPIをお客様が使う場面は極めて限定的でしょう
- お客様がAPIを駆使して独自のCMSを作るような用途は想定していません
- オーサリングAPIを使う場合は、最初にログインのAPIを叩き、認証を済ませておく必要があります
- オーサリングAPIを使ってコンテンツへアクセスすることも可否で言えば可能ですが、キャッシュもCDNも使用していないので性能的に不利です。（要は、お客様の「本番」のアプリがこちらを使うことは想定していません）

### APIの種類の分類

WCHが提供するAPI群を「扱う対象」で見ると以下のような関係になります。例えば「アセット」や「カテゴリー」の「作成・参照・更新・削除（CRUD)」の操作はオーサリング時に行うものなのでオーサリングAPIには存在しますが、お客様の本番アプリケーションでは同様の操作は不要ですので、デリバリーAPIには存在しません。

![image](https://qiita-image-store.s3.amazonaws.com/0/108535/beccac94-8e1f-a44e-83e7-01457da79c07.png)

「コンテンツ」「サーチ」などオーサリングとデリバリーの両方で扱うものには各々にAPI群がありますが、機能が同じ場合はインターフェース(URL)は一部を除き、同じです。以下はサーチの例です

- https://{DomainName}/{path}/**authoring**/v1/search
- https://{DomainName}/{path}/**delivery**/v1/search

### APIのURLの構造

以下がWCHのAPIの構造です。URLの先頭の部分の値はオーサリングUIのユーザー情報の「ハブ情報」より入手できます。（これはテナント毎に固定です）後続の機能を記述する箇所の構文はAPIリファレンスに定義されています。(例えばデリバリー環境でのサーチでは/delivery/v1/searchなど）APIによっては更にQueryパラメータを記述できます。（オプションです）
![image](https://qiita-image-store.s3.amazonaws.com/0/108535/b3cbeb94-4b64-1f43-417d-5790b706aaa9.png)

### APIリファレンス
WCHのAPIリファレンスは[ここ](https://developer.ibm.com/api/view/id-618:title-IBM_Watson_Content_Hub_API#doc)です。解説ドキュメントと同じ場所にあります。左側より機能を選択すると真ん中にAPIの説明があり、右側には環境別の構文実装例が表示されます。

![image](https://qiita-image-store.s3.amazonaws.com/0/108535/f4855153-616e-59bb-2fcd-812f87f11ab5.png)

# <i class="fa fa-user-circle-o" style="font-size:1em;"></i>やってみる

では「１．オーサリング編」で定義したコンテンツをブラウザーで表示させましょう。入門編なのでシンプル/わかりやすさを重視し、あえてUIフレームワークは一切使わず、HTMLとJavaScriptのみで実装しました。

## HTMLのコピペ
以下のHTMLをコピペしてお好きな名前で保存してください。
処理としては
① JavaScript内でRESTで「Running」のコンテンツをGET
② レスポンスのJSONをParseしてTitle, Image01/02のレンディションbannerのURLを抽出
③ innerHTML でHTMLタグと共にレンダリング
しています。

```html:Running.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Running</title>
</head>

<body class="container" onload="myscript();">
    <div id="demo">
    </div>
    <script type="text/javascript">
    function myscript() {
        const req1 = new XMLHttpRequest();
        req1.overrideMimeType("application/json");
        req1.withCredentials = true;
        // ***** 要変更! ****
        //　1) Watson Content Hubの「ユーザー名」-「ハブ情報」よりドメイン・ネームをコピペします
        var BaseURL = "https://my14.digitalexperience.ibm.com";
        //　2) Watson Content Hubのコンテンツ「Running」の最下にある「API情報」ボタンを押して「配信URL」をコピペします
        req1.open("GET", "https://my14.digitalexperience.ibm.com/api/554fe6b3-1303-43ed-a5e5-b089829a3161/delivery/v1/content/0fe7b3cc-3c9d-4eae-8fa2-dd101d3acf32", false); // false =JQueryを使わないので同期モード(非推奨)で実行します
        req1.onreadystatechange = req1Done;
        req1.send();

        function req1Done() {
            if (req1.status === 200) {
                // innerHTMLは性能面でNGですが、わかりやすさを優先して使っています。
                myObj = JSON.parse(req1.responseText);
                // レスポンスからTitleとbannerURLの値を取得してHTMLとして表示
                document.getElementById("demo").innerHTML +=
                    "<h1>" + myObj.elements.title.value + "</h1>";
                document.getElementById("demo").innerHTML +=
                    "<br><br><img src=\"" + BaseURL + myObj.elements.image01.renditions.banner.url + "\"/>";
                document.getElementById("demo").innerHTML +=
                    "<br><br><img src=\"" + BaseURL + myObj.elements.image02.renditions.banner.url + "\"/>";
            } else {
                console.log("req1.status = " + req1.status);
            }
        }

    }
    </script>
</body>

```

### HTML中の環境固有値のアップデート

コード中の ** ***** 要変更! **** **とある２か所をご自身の環境のものに書き換えて保存してください。（コピー元はコメントに書いてあります）

### HTMLをブラウザーで開く

HTMLファイルをブラウザーで開きます。以下のような画面が表示されましたか？

:warning: 通常はこの方式はCORS制約に抵触しますので、動きません。今回は[1.オーサリング編](http://qiita.com/ishida330/items/156350b28b3657b5b413)の環境設定でCORSのトラステッド・ドメインに「*」（全てのドメインからの要求を受け入れる）を指定したから動いています。セキュリティ的に問題あり、なので後で直しておいてください。

![image](https://qiita-image-store.s3.amazonaws.com/0/108535/f7ff2d3d-ca1d-9ebd-287d-d4e0f6c67bb1.png)

ご参考までに、当記事の末尾に実際のJSONレスポンスの例を掲載しておきますね。(ブラウザーのデバッグ機能(Chromeの場合PF12) でも見られますが)

# <i class="fa fa-search" style="font-size:1em;"></i>サーチ（絞り込み/フィルタリング)

直前の例では「コンテンツのメタデータ定義を全部下さい」という要求を投げていますが、実際のアプリケーションではコンテンツを扱う際に以下のような様々な絞り込み/フィルタリングをしたい場面が多いと思います。

- ある特定のイメージ画像だけ欲しい
- ファイルの種類として動画だけほしい
- （「ファッション」など）特定のカテゴリーに属するイメージ画像だけ欲しい
- ページングで10件ずつ欲しい
- 量が多いので最大100件に絞りたい
- 表示の順番を最新日付順にソートしたい 

このような様々な条件による絞り込み操作を提供するのが**サーチ・サービス**です。サーチ・サービスのAPIで検索条件を照会パラメーターとして記述すれば、条件に合ったコンテンツの情報のみを絞り込んで入手できます。

文献: [Search in the delivery collection](https://developer.ibm.com/api/view/id-618:title-IBM_Watson_Content_Hub_API#id25400)

上記のAPIドキュメントによるとサーチのURL形式は https://{DomainName}/{path}/**delivery/v1/search**ですが、後続の照会パラメータ―で非常に多くの条件を複合的に記述できます。

照会パラメーターは以下が基本形です(有効なkeyとvalueの説明は上記APIドキュメントに記述があります）
```
q={key}:{value}
```
更に条件を付けたければ&で繋ぎます
```
q={key}:{value}&{key}:{value}&{key}:{value}...
```
たとえばWCHに格納されている全要素から動画だけを抜き出す表現は下記です。
```
https://{DomainName}/{path}/delivery/v1/search?q=assetType:video
または
https://{DomainName}/api/{Content Hub ID}/delivery/v1/search?q=assetType:video
```
:pencil:ご参考ですが、WCHでは基本的な「Search」サービス以外に[「Context Search」サービス](https://developer.ibm.com/api/view/id-618:title-IBM_Watson_Content_Hub_API#id25438)もあります。「Context Search」は「コンテキスト（文脈）」に沿ったサーチという意味で、現時点では以下の２種類が提供されています。

1) 言語( accept-language )
    特定の言語のアセットのみに絞り込むもの
2) 位置情報( location )     
    アセットに位置情報がある場合、「半径 X km以内のもの」などロケーションに基づいたサーチを可能にするもの
    
### やってみる

では試しにタグ「jogging」を設定されたイメージ画像のみを抜き出しましょう。(タグの設定は[1.オーサリング編](http://qiita.com/ishida330/items/156350b28b3657b5b413)で行いました)

構文としては下記で、[デリバリーのSearch API](https://developer.ibm.com/api/view/id-618:title-IBM_Watson_Content_Hub_API#id25400)を使い、条件としてtags:joggingかつassetType:imageを指定してGET要求を出します。

```
https://{DomainName}/api/{Content Hub ID}/delivery/v1/search?q=tags:jogging&assetType:image
```
上記をコピーして({DomainName}や{Content Hub ID}を皆様の環境に合わせて書き換えた上でお好きなブラウザーのアドレスバーに貼り付けてリロードしてください。
![image](https://qiita-image-store.s3.amazonaws.com/0/108535/8fda4f8a-2826-42bb-a962-7a87852d9bce.png)

上記のようにJSONが返りましたでしょうか?　あとは先程と同様にURLやPATHからHTMLを生成するのは容易ですね。今は「タグ」で絞り込みましたが、絞り込みの条件は非常に細かく指定できます。（業務的な分類で分けておきたければ「分類法」(Taxonomy)も使えます）当記事の末尾に絞り込みの例を記載しておきますので別途ご参照ください。

なお、今回はブラウザーを使いましたが、実際にAPIを色々確認する際は、Chromeのアドインである[Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=ja)を使うとJSONを整形してくれたり、設定を保存できるので何かと便利です。

![image](https://qiita-image-store.s3.amazonaws.com/0/108535/41da90b8-2acb-37d2-c49e-d7facf23a193.png)


# <i class="fa fa-play" style="font-size:1em;"></i>終わりに
お疲れ様でした！当記事では「入門編」としてWatcon Content Hubの最もベーシックなところに絞ってご紹介しました。より実践的でカッコいいサンプルは下記サイトに多数公開されています。AngularやHandlebarsなどUIフレームワークの例、モバイルでの利用例など多数掲載されていますしソースもgithubに公開されていますので、ご興味があれば、ぜひアクセスしてみてください。

- [Live Samples Gallery](http://samples.watsoncontenthub.io/sample-pages/) 

# <i class="fa fa-step-forward" style="font-size:1em;"></i>ご参考1)「やってみる」のJSONレスポンス

例で利用している以下のJSON表現を下記のレスポンスと照らし合わせてみると、よりご理解が進むと思います。
- myObj.elements.title.value 
- myObj.elements.image01.renditions.banner.url
- myObj.elements.image02.renditions.banner.url


```json:Response
{
    "name": "Running Item",
    "description": "",
    "classification": "content",
    "typeId": "468d96ac-c162-4b1c-a48a-3ccdba0b2479",
    "locale": "ja-JP",
    "lastModified": "2017-06-15T08:14:58.178Z",
    "lastModifierId": "a15e8af1-e304-4c89-abb8-bb7576f62421",
    "created": "2017-06-15T07:45:01.412Z",
    "creatorId": "a15e8af1-e304-4c89-abb8-xxxxxxxxxxxx",
    "tags": [],
    "keywords": [],
    "status": "ready",
    "elements": {
        "title": {
            "elementType": "text",
            "value": "ランニングは最高！"
        },
        "image01": {
            "elementType": "image",
            "renditions": {
                "default": {
                    "renditionId": "r=f992311ec0a6b26fb9ed71e0813818e4&a=65f06dee-45ba-449e-a378-d609aea739cf",
                    "source": "/delivery/v1/resources/f992311ec0a6b26fb9ed71e0813818e4",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/65/65f06dee-45ba-449e-a378-d609aea739cf/Man%20jogging%20on%20beach.jpg"
                },
                "standard": {
                    "renditionId": "2df90763-b577-4d05-99eb-fc35cc60e0a5",
                    "source": "/delivery/v1/resources/f992311ec0a6b26fb9ed71e0813818e4?resize=901px:600px&crop=800:600;51,
                    0 ",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/65/65f06dee-45ba-449e-a378-d609aea739cf/Man%20jogging%20on%20beach.jpg?resize=901px%3A600px&crop=800%3A600%3B51%2C0"
                },
                "banner": {
                    "renditionId": "aeffa37b-8dae-4813-a741-6852107a647a",
                    "source": "/delivery/v1/resources/f992311ec0a6b26fb9ed71e0813818e4?resize=1408px:938px&crop=800:200;7,
                    330 ",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/65/65f06dee-45ba-449e-a378-d609aea739cf/Man%20jogging%20on%20beach.jpg?resize=1408px%3A938px&crop=800%3A200%3B7%2C330"
                }
            },
            "asset": {
                "id": "65f06dee-45ba-449e-a378-d609aea739cf",
                "resourceUri": "/delivery/v1/resources/f992311ec0a6b26fb9ed71e0813818e4",
                "fileName": "Man jogging on beach.jpg",
                "fileSize": 3703719,
                "mediaType": "image/jpeg"
            },
            "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/65/65f06dee-45ba-449e-a378-d609aea739cf/Man%20jogging%20on%20beach.jpg"
        },
        "image02": {
            "elementType": "image",
            "renditions": {
                "default": {
                    "renditionId": "r=a68e30c979433cfa7fe8536a4d2dda36&a=2a278825-4b64-4c7e-9cd9-9ee9b56b7382",
                    "source": "/delivery/v1/resources/a68e30c979433cfa7fe8536a4d2dda36",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/2a/2a278825-4b64-4c7e-9cd9-9ee9b56b7382/Young%20runner%20stretching.jpg"
                },
                "standard": {
                    "renditionId": "cf5d6677-7db1-446f-845b-589aedcd6fe5",
                    "source": "/delivery/v1/resources/a68e30c979433cfa7fe8536a4d2dda36?resize=901px:600px&crop=800:600;50,
                    0 ",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/2a/2a278825-4b64-4c7e-9cd9-9ee9b56b7382/Young%20runner%20stretching.jpg?resize=901px%3A600px&crop=800%3A600%3B50%2C0"
                },
                "banner": {
                    "renditionId": "efa03961-ba99-4dc4-8434-caae76517b12",
                    "source": "/delivery/v1/resources/a68e30c979433cfa7fe8536a4d2dda36?resize=800px:533px&crop=800:200;0,
                    167 ",
                    "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/2a/2a278825-4b64-4c7e-9cd9-9ee9b56b7382/Young%20runner%20stretching.jpg?resize=800px%3A533px&crop=800%3A200%3B0%2C167"
                }
            },
            "asset": {
                "id": "2a278825-4b64-4c7e-9cd9-9ee9b56b7382",
                "resourceUri": "/delivery/v1/resources/a68e30c979433cfa7fe8536a4d2dda36",
                "fileName": "Young runner stretching.jpg",
                "fileSize": 8319743,
                "mediaType": "image/jpeg"
            },
            "url": "/554fe6b3-1303-43ed-a5e5-b089829a3161/dxdam/2a/2a278825-4b64-4c7e-9cd9-9ee9b56b7382/Young%20runner%20stretching.jpg"
        }
    },
    "type": "Running",
    "id": "0fe7b3cc-3c9d-4eae-8fa2-dd101d3acf32",
    "rev": "1-7f26761e612774661c3c18ff030db00f"
}
```

# ご参考2) デリバリーAPIでの絞り込み指定例

■(WCH上に格納されている）全要素 - デフォルトでは最初の10件のみ
- *...複数文字にマッチ  ?...一文字にマッチ

```
q=*:*
```

■(WCH上に格納されている）全要素のうち最初の16件 
```
q=*:*&rows=16 
```
■全てのアセット
```
q=classification:asset 
```
■全アセットのうち、イメージのみ 
- AND, OR, NOTは大文字で。+ (requires) と – (similar to NOT)も利用可能 


```
q=classification:asset AND assetType:image 
```
または
```
q=classification:asset&fq=assetType:image
```
■全アセットのうち、イメージ**以外** 
```
q=classification:asset NOT assetType:image 
```
■全アセットのうち、イメージのみを最終更新日付の降順で
```
q=classification:asset&fq=assetType:image&sort=lastModified%20desc 
```
■全アセットのうち、イメージのみを2番目から3つ
```
q=classification:asset&fq=assetType:image&rows=3&start=2 
```
■全アセットのうち、特定の日時以降に更新されたもの
- 範囲 [ ] は両端を含む　{ }は両端を含まない count:{1 TO 10] のように併用可
```
q=classification:asset&fq=lastModified:[2016-11-23T12:28:46-650Z TO NOW] 
```
■JSONドキュメント中に文字列「 Ahoi 」を含むコンテンツ（全文検索）
:warning:検索時にインデックスを使わないので性能に注意！

```
q=classification:content&fq=document:*Ahoi* 
```