トピックベースのメッセージング
トピックベースのメッセージング(TBM)を使用すると、同じトピックをサブスクライブしているユーザーのグループにメッセージを送信できます。このメッセージは、マルチキャスト通信を通じて送信されます。アプリがデバイスにインストールされると、そのアプリのインスタンスにデバイス登録IDと呼ばれる一意の識別子が割り当てられます。このIDを使用して個々のデバイスにトピックベースのメッセージを配信することで、単一のAPI呼び出しで複数のアプリインスタンスにメッセージを送信できます。メッセージは、個々のデバイス登録IDではなくトピックに対して送信されます。ユーザーがそのトピックをサブスクライブしていれば、Amazon Device Messaging(ADM)がそのトピックに関連付けられたすべてのデバイス登録IDにメッセージをルーティングします。メッセージの種類には、データメッセージ、通知メッセージ、データを含む通知メッセージがあります。
トピックを使用することで、特定のユーザーセグメントのエンゲージメントを高めることができます。たとえば、天気予報アプリの場合、ユーザーは自分の所在地のトピックを選択して、その地域の天気の変化に関する通知を受信することができます。ビデオストリーミングアプリの場合は、ユーザーが興味のあるジャンルを登録すれば、そのジャンルの新しい映画やシリーズがリリースされるたびに自動的に最新情報を受け取ることができます。
- トピックベースのメッセージングに関する考慮事項
- トピックベースのメッセージングへのセキュリティプロファイルの登録
- クライアントアプリインスタンスによるトピックのサブスクライブ/サブスクライブ解除
- トピックへのメッセージの送信
トピックベースのメッセージングに関する考慮事項
トピックベースのメッセージに関する重要な考慮事項を以下に示します。
- トピックベースのメッセージは特定のセキュリティプロファイルをターゲットにします。つまり、同じセキュリティプロファイルに登録された複数のアプリのインスタンスにトピックからメッセージを送信できます。
- Amazon Device Messaging(ADM)では、トピックに関して以下の制限が適用されます。
    - 1つのセキュリティプロファイルに割り当てることができるトピックの数は、最大100個です。
- 1つのアプリインスタンスがサブスクライブできるトピックの数は、最大100個です。
- 1つのトピックをサブスクライブできるアプリインスタンスの数は、最大10000個です。
 
- トピックベースのメッセージでは、レイテンシの低減よりもスループットの向上が重視されます。個々のデバイスにメッセージを送信するには、トピックではなくデバイス登録IDをメッセージのターゲットに設定してください。
- トピックベースのメッセージングは、天気予報や株価など、一般に公開されているリソースに最適です。
トピックベースのメッセージングへのセキュリティプロファイルの登録
トピックベースのメッセージングでは、以下の操作を実行できます。
- デバイス登録IDによるトピックのサブスクライブ
- デバイス登録IDによるトピックのサブスクライブ解除
- トピックへのメッセージの送信
前提条件
トピックベースのメッセージングを使用するには、以下の前提条件を満たす必要があります。
- アプリインスタンスの登録IDを取得・保存していること。このプロセスのアプリ側での詳細については、アプリの統合方法を参照してください。
- クライアント認証情報と現在のアクセストークンが交換済みであること。詳細については、アクセストークンのリクエスト方法を参照してください。
これらの前提条件を満たしたら、プログラムでTopicBasedMessagingに対して、生成されたaccessTokenに関連付けられたセキュリティプロファイルを登録できます。
リクエスト形式
セキュリティプロファイルをトピックベースのメッセージングに登録するには、サーバーコンポーネント(以降「サーバー」と呼びます)から以下のようなHTTP POSTリクエストを発行する必要があります。
POST /v1/messaging/topic/registrations HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (アクセストークン)
Content-Type: application/json
Accept: application/json
{
    "clientSecret":"クライアントシークレット"
}
POST URLは2行目(Host)と1行目(POST)で構成されます。これらを組み合わせると、以下のような完全なURLを取得できます。
https://api.amazon.com/v1/messaging/topic/registrations
リクエスト要件
リクエスト自体は、ヘッダーとメッセージ本文で構成されています。
ヘッダーフィールド
ヘッダーには、以下のフィールドを含める必要があります。
| フィールド | 概要 | 例 | 
|---|---|---|
| Authorization | 現在のアクセストークンを指定します。有効値: Bearer (アクセストークン) | Authorization: Bearer <アクセストークン> | 
| Content-Type | 有効値: application/json | Content-Type: application/json | 
| Accept | 有効値: application/json | Accept: application/json | 
メッセージ本文のパラメーター
メッセージ本文のコンテンツとして、JSONObjectに以下のパラメーターを含む文字列を指定します。
| パラメーター | 説明 | 例 | 
|---|---|---|
| clientSecret | クライアント認証情報の「クライアントシークレット」部分。 | clientSecret=<クライアントシークレット> | 
レスポンス形式
ADMサーバーは、POSTリクエストメッセージを正常に受信して解釈した後、以下のようなHTTPレスポンスメッセージを送信します。
HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type:application/json
content-length:140
{
    "message": "The security profile amzn1.application.<32個の16進文字> is registered with TopicBasedMessaging."
}
クライアント認証情報のセキュリティプロファイルがTopicBasedMessagingに登録された場合、ADMからステータスコード200が返されます。ステータスコード200を受け取った場合は、レスポンスメッセージのJSONObjectに以下のパラメーターが含まれています。
- message:- accessTokenの作成に使用した認証情報とメッセージ本文で使用された- clientSecretに関連付けられたセキュリティプロファイルが記載されます。セキュリティプロファイルが- TopicBasedMessagingに登録されていることを示すレスポンスになります。
クライアント認証情報のセキュリティプロファイルがTopicBasedMessagingに登録されなかった場合、ADMからエラーコードが返されます。200以外のコードの場合は、レスポンスメッセージのJSONObject本文に以下のパラメーターが含まれていることがあります。
- reason: リクエストが受理されなかった理由。
各エラーのステータスコードの詳細については、以下を参照してください。
ADMエラーのステータスコード
| コード | 説明 | 例 | 
|---|---|---|
| 400 | メッセージ本文の clientSecretが、accessTokenの作成に使用したものとは異なります。送信元は、accessTokenの作成に使用したものと同じclientSecretをメッセージ本文で使用する必要があります。 | "reason": "The given clientSecret is not associated with the security profile amzn1.application.<32個の16進文字>" | 
| 401 | 提供されたアクセストークンが無効です。送信元は、アクセストークンを更新する必要があります。更新の手順については、アクセストークンのリクエスト方法を参照してください。 | "reason":"AccessTokenExpired" | 
| 429 | リクエスト側が、許容されている最大メッセージレートを超過しました。送信元は、レスポンスに含まれている Retry-Afterヘッダーの指示に従って、後で再試行できます。ADMでは、一定時間内に送信されるメッセージ数を制限することで、高可用性が確保されています。具体的なキャパシティ要件がある場合は、お問い合わせから以下の情報をお知らせください。- 氏名 - 会社名 - Eメールアドレス - 必要なTPS(1秒あたりのトランザクション数)制限 - 理由 | "reason":"MaxRateExceeded" | 
| 500 | 内部サーバーエラーが発生しました。リクエスト側は、レスポンスに含まれている Retry-Afterヘッダーに従って、後で再試行できます。 | なし | 
| 503 | サーバーが一時的に利用できない状態になっています。リクエスト側は、レスポンスに含まれている Retry-Afterヘッダーの指示に従って、後で再試行できます。 | なし | 
レスポンスヘッダーフィールド
| フィールド | 概要 | 例 | 
|---|---|---|
| X-Amzn-RequestId | リクエストを一意に識別するためにADMによって作成された値。万が一、ADMに問題が発生した場合は、Amazon側でこの値を用いてトラブルシューティングを行います。 | X-Amzn-RequestId: <Amazon RequestId> | 
| Retry-After | エラーレスポンス429、500、503が発生した場合に、このフィールドが返されます。Retry-Afterは、サービスが利用できない時間の長さ(予測)を示します。有効値は、レスポンス後の10進法の秒数またはHTTP形式の日付です。この値の有効な形式については、HTTP/1.1仕様のセクション10.2.3を参照してください。 | Retry-After: 30 | 
| Content-Type | リソースのコンテンツタイプ: application/json | Content-Type: application/json | 
TBMの登録リクエスト
以下は、サーバーソフトウェアでTopicBasedMessagingにセキュリティプロファイルを登録するリクエストを行い、ADMサーバーレスポンスを処理する方法の例です。
/**
 * セキュリティプロファイルをTopicBasedMessagingに登録するようADMにリクエストします。
 */
public void tbmRegistration(String clientSecret, String accessToken) throws Exception
{
    // メッセージのJSONペイロード表現。
    JSONObject payload = new JSONObject();
    // ヘッダーに指定されたaccessTokenを作成するために使用するclientSecret値を追加します。
    payload.put("clientSecret", clientSecret);
    // メッセージをJSONオブジェクトから文字列に変換します。
    String payloadString = payload.toString();
    // ベースURLを設定します。
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/registrations";
    URL admUrl = new URL(admUrlTemplate);
    // POSTリクエスト用のHTTPS接続を生成します。HTTP接続は
    // 行えません。
    HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
    conn.setRequestMethod("POST");
    conn.setDoOutput(true);
    // コンテンツタイプを設定し、ヘッダーを受け取ります。
    conn.setRequestProperty("content-type", "application/json");
    conn.setRequestProperty("accept", "application/json");
    
    // ヘッダーとして認証トークンを追加します。
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);
    // 接続の出力ストリームを取得し、メッセージペイロードを書き込みます。
    OutputStream os = conn.getOutputStream();
    os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
    os.flush();
    conn.connect();
    // 接続からレスポンスコードを取得します。
    int responseCode = conn.getResponseCode();
    // 失敗のレスポンスを受信したかどうか確認し、受信した場合は失敗の理由を取得します。
    if( responseCode != 200)
    {
        if( responseCode == 401 )
        {
            // レスポンスコード401を受信した場合は、アクセストークンの期限が切れていることを意味します。トークンを更新してください。
            // このリクエストは再試行できます。
        }
        String errorContent = parseResponse(conn.getErrorStream());
        throw new RuntimeException(String.format("ERROR: The request to register the security profile with TBM has failed with a " +
                                         "%d response code, with the following message: %s",
                                         responseCode, errorContent));
    }
    else
    {
        // リクエストに成功しました。レスポンスには、TopicBasedMessagingに登録されているセキュリティプロファイルが記載されています。
        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);
        String message = parsedObject.getString("message");
        
        // メッセージからセキュリティプロファイルを抽出して、それがリクエストヘッダーで使用されている
        // accessTokenの作成に使用したものと同じかどうかを確認できます。
        
    }
}
private String parseResponse(InputStream in) throws Exception
{
    // 入力ストリームから読み取り、文字列に変換します。
    InputStreamReader inputStream = new InputStreamReader(in);
    BufferedReader buff = new BufferedReader(inputStream);
    StringBuilder sb = new StringBuilder();
    String line = buff.readLine();
    while(line != null)
   {
      sb.append(line);
      line = buff.readLine();
    }
    return sb.toString();
}
クライアントアプリインスタンスによるトピックのサブスクライブ/サブスクライブ解除
TopicBasedMessagingに登録する必要があります。詳細については、トピックベースのメッセージングへのセキュリティプロファイルの登録を参照してください。クライアントアプリは、既存のトピックをサブスクライブするか、新しいトピックを作成できます。クライアントアプリが、関連付けられたセキュリティプロファイルに存在しない新しいトピックをサブスクライブすると、新しいトピックが作成されます。そのセキュリティプロファイルに関連付けられたアプリであれば、そのトピックをサブスクライブできます。
アプリでトピックをサブスクライブする場合は、ADMでトピック名を指定してsubscribeToTopic()を呼び出します。アプリはonSubscribe()またはonSubscribeError()を介して通知を受け取ります。アプリは、com.amazon.device.messaging.ADMMessageHandlerJobBaseクラスとcom.amazon.device.messaging.ADMMessageHandlerBaseクラスで定義されているこれらのコールバックメソッドをオーバーライドする必要があります。
サブスクライブの例
final ADM adm = new ADM(this);
// subscribeToTopic()は非同期です。subscribeToTopic()が成功した場合、
// アプリはonSubscribe()コールバックを介して通知を受け取ります。
// subscribeToTopic()が失敗した場合はonSubscribeError()になります。
adm.subscribeToTopic("weather");
アプリでトピックのサブスクライブを解除する場合は、トピック名を指定してADMのunsubscribeFromTopic()を呼び出します。アプリはonUnsubscribe()またはonUnsubscribeError()を介して通知を受け取ります。アプリは、com.amazon.device.messaging.ADMMessageHandlerJobBaseクラスとcom.amazon.device.messaging.ADMMessageHandlerBaseクラスで定義されているこれらのコールバックメソッドをオーバーライドする必要があります。
サブスクライブ解除の例
final ADM adm = new ADM(this);
// unsubscribeFromTopic()は非同期です。unsubscribeFromTopic()が成功した場合、
// アプリはonUnsubscribe()コールバックを介して通知を受け取ります。
// unsubscribeFromTopic()が失敗した場合はonUnsubscribeError()になります。
adm.unsubscribeFromTopic("weather");
これらの新しいAPIは、古いFire OSデバイスでは利用できないことがあります。その場合、デバイス上のアプリがクラッシュするおそれがあります。これを回避するには、以下に示すようにtry/catchブロックを使用してこれらのAPIを呼び出します。
try {
    Log.d(TAG, "subscribeToTopic: Trying to subscribe to topic: " + topic);
    adm.subscribeToTopic(topic);
} catch (Error e) {
    Log.d(TAG, "subscribeToTopic: Error Caught and Error Message is " + e.getMessage());
}
コールバックメソッドの説明
| コールバックメソッド | 説明 | 
|---|---|
| onSubscribe() | トピックのサブスクリプションが成功すると、パラメーターのトピック(アプリがサブスクライブしたトピック)およびアプリコンテキストと共に呼び出されます。 | 
| onSubscribeError() | サブスクリプションリクエストが失敗したときに、パラメーターのトピック(アプリがサブスクライブしようとしていたトピック)、エラーID、アプリコンテキストと共に呼び出されます。 | 
| onUnsubscribe() | トピックのサブスクリプション解除が成功すると、パラメーターのトピック(アプリのサブスクリプションが解除されたトピック)およびアプリコンテキストと共に呼び出されます。 | 
| onUnsubscribeError() | トピックのサブスクリプション解除が失敗したときに、パラメーターのトピック(アプリがサブスクリプションを解除しようとしていたトピック)、エラーID、アプリコンテキストと共に呼び出されます。 | 
エラーIDと説明
| エラーID | 説明 | 
|---|---|
| INVALID_MANIFEST | マニフェストに必要な権限/ブロードキャストレシーバーインテントフィルターが定義されていません。統合の手順を確認してマニフェストファイルを更新してください。 | 
| INTERNAL_ERROR | HTTP 500ステータスコードに類似した、未処理の内部エラー。必要な対応はありません。 | 
| INVALID_TOPIC | 指定されたトピックが有効ではありません。トピックはnullにすることはできません。また、トピックの命名パターン( [a-zA-Z0-9-_.~%]{1,100})に従う必要があります。 | 
| NOT_REGISTERED_WITH_TBM | アプリがTBMに登録されていません。トピックベースのメッセージにアプリを登録してください。 | 
| UNREGISTERED | アプリが登録されていないか、登録IDが無効です。再度登録するには、ADMの startRegister()を呼び出してください。 | 
| ALREADY_SUBSCRIBED | アプリは既にそのトピックをサブスクライブしています。再度サブスクライブしないでください。 | 
| NOT_SUBSCRIBED | アプリのサブスクリプションが既に解除されているか、指定されたトピックが存在しません。このトピックのサブスクリプションを解除しようとしないでください。 | 
| MAXIMUM_SUBSCRIPTION_EXCEEDED | サブスクリプションの上限を超えており、以下のいずれかに該当します。 (i) セキュリティプロファイルに100個のトピックが割り当てられている (ii) アプリインスタンスが100個のトピックをサブスクライブしている (iii) トピックが10000個のアプリインスタンス/registrationIdでサブスクライブされている | 
| TOO_MANY_REQUESTS | サブスクライブまたはサブスクライブ解除のリクエストが多すぎます。30秒後に再試行してください。 | 
| SERVICE_NOT_AVAILABLE | ADMサービスと通信できません。非常にまれなエラーです。 | 
コールバックメソッドのサンプルコード
public class MyADMMessageHandler extends ADMMessageHandlerJobBase{
    @Override
    protected void onSubscribe(final Context context, final String topic) {
        // サブスクリプションが成功した場合のロジックをここに記述します
        // 例:ユーザーに通知する、
        // サーバーに詳細情報を送信する
    }
    @Override
    protected void onSubscribeError(final Context context, final String topic, final String errorId) {
        // エラーIDに基づいてサブスクリプションがエラーになった場合のロジックをここに記述します
    }
    @Override
    protected void onUnsubscribe(final Context context, final String topic) {
        // サブスクリプション解除が成功した場合のロジックをここに記述します
        // 例:ユーザーに通知する、
        // サーバーに詳細情報を送信する
    }
    @Override
    protected void onUnsubscribeError(final Context context, final String topic, final String errorId) {
        // エラーIDに基づいてサブスクリプション解除がエラーになった場合のロジックをここに記述します
    }
}
トピックメッセージの受信と処理
ADMは、単一の登録IDに配信する場合と同じ方法でトピックにメッセージを配信します。詳細については、ADMメッセージのタイプを参照してください。
トピックへのメッセージの送信
前提条件
トピックにメッセージを送信するには、以下の前提条件を満たす必要があります。
- トピックベースのメッセージングにセキュリティプロファイルを登録している。
- メッセージの送信先トピックをクライアントアプリインスタンスでサブスクライブしている。
これらの前提条件を満たしたら、プログラムでADMを使用してトピックにメッセージを送信できます。
リクエスト形式
トピックにメッセージを送信するために、サーバーから以下のようなHTTP POSTリクエストが発行されます。
POST /v1/messaging/topic/messages HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (アクセストークン)
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept: application/json
X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0
{
    "data":{"key1":"value1","key2":"value2"},
    "notification":{"title":"ADM Message Notification","body":"We have a new offer for you!"},
    "consolidationKey":"Some Key",
    "expiresAfter":86400,
    "priority": "high",
    "topic":"SomeTopic"
}
POST URLは2行目(Host)と1行目(POST)で構成されます。これらを組み合わせると、以下のような完全なURLを取得できます。
https://api.amazon.com/v1/messaging/topic/messages
リクエスト要件
リクエスト自体は、ヘッダーとメッセージ本文で構成されています。
ヘッダーフィールド
ヘッダーには、以下のフィールドを含める必要があります。
| フィールド | 概要 | 例 | 
|---|---|---|
| Authorization | 現在のアクセストークンを指定します。有効値: Bearer (アクセストークン) | Authorization: <アクセストークン> | 
| Content-Type | 有効値: application/json | Content-Type: application/json | 
| X-Amzn-Type-Version | 有効値: com.amazon.device.messaging.ADMMessage@1.0 | X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0 | 
| Accept | 有効値: application/json | Accept: application/json | 
| X-Amzn-Accept-Type | 有効値: com.amazon.device.messaging.ADMSendResult@1.0 | X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0 | 
メッセージ本文のパラメーター
メッセージ本文のコンテンツとして、JSONObjectに以下のパラメーターを含む文字列を指定します。
| パラメーター | 説明 | 例 | 
|---|---|---|
| data | メッセージと共に送信するペイロードデータ。データはJSON形式のキーと値のペアから成り、いずれも String値である必要があります。キーと値のほか、これらを囲む引用符、キーと値の間を区切る「:」文字、ペアを区切るコンマ、フィールドを囲む中かっこを含めた合計データサイズが、6KBを超えることはできません(メッセージ本文にnotificationフィールドがない場合)。キーと値のペア間の空白は、ペイロードサイズの計算に含まれません。同期メッセージのように、メッセージにペイロードが含まれない場合は、空のオブジェクト("data":{}など)を渡すことができます。送信メッセージにデータと通知のペイロード両方が含まれている場合、合計サイズが6KBを超えることはできません。 | "data":{ "from":"Sam", "message":"Hey, Max.How are you?", "time":"10/26/2012 09:10:00" } | 
| notification | メッセージと共に送信されるペイロード通知。通知メッセージでは、事前に定義されたキーと値のペアを使用する必要があります。ADM通知でサポートされるキーと値のペアについては、こちらを参照してください。 メッセージにデータと通知のペイロード両方が含まれている場合、合計サイズが6KBを超えることはできません。 | "notification":{"title":"ADM Message Notification","body":"We have a new offer for you!"} | 
| priority | オプション値。送信メッセージの優先度は、normal(通常)とhigh(高)の2つです。メッセージのデフォルトの優先度はnormalです。優先度がnormalのメッセージは、アプリがフォアグラウンドにある場合はすぐに配信できます。デバイスがDozeモードの場合は、次のメンテナンス時間まで配信が遅れることがあります。優先度がhighのメッセージの場合、ADMはDozeモードであってもすぐにアプリにメッセージの配信を試みます。アプリが1日に受信できる優先度highのメッセージの数は、アプリスタンバイバケットに基づいて制限されます。デフォルト値はnormalです。 | "priority": "high" | 
| consolidationKey | オプション値。これは、複数のメッセージが論理的に同一であり、それまでエンキューされていたメッセージに優先してこの新しいメッセージを使用できることを示すための任意の文字列です。それまでエンキューされていたメッセージが配信されないという保証はありません。連結キーは64文字以下にする必要があります。 | "consolidationKey":"SyncNow" | 
| expiresAfter | オプション値。デバイスがオフラインの場合に、ADMがメッセージを保持する秒数を表します。この時間が経過すると、メッセージは破棄される可能性があります。有効値の範囲は1(1秒)~2678400(31日間)で、デフォルト値は604800(1週間)です。 | "expiresAfter":86400 | 
| topic | 必須の値。アプリインスタンスがサブスクライブし、メッセージの送信先となるトピック。文字列値は、正規表現パターン( [a-zA-Z0-9-_.~%]{1,100})を満たす必要があります。 | "topic":"SomeTopic" | 
レスポンス形式
ADMサーバーは、POSTリクエストメッセージを正常に受信して解釈した後、以下のようなHTTPレスポンスメッセージを送信します。
HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type: application/json
Content-Length: 308
{
    "messageId":"<メッセージID>"
}
メッセージが受理され、デバイスへの配信用にエンキューされると、ADMからステータスコード200が返されます。その場合、レスポンスメッセージのJSONObjectに以下のパラメーターが含まれます。
- messageId: 送信メッセージとメッセージの送信先トピックに関連付けられたID。
メッセージが正常に受理されなかった場合は、ADMからエラー(200以外)のステータスコードが返されます。その場合、レスポンスメッセージのJSONObjectの本文に以下のパラメーターが含まれていることがあります。
- reason: リクエストが受理されなかった理由。
ADMエラーのステータスコード
ADMエラーのステータスコードとその説明を以下に示します。
| コード | 説明 | 例 | 
|---|---|---|
| 400 | 以下のような状況では、リクエストがレスポンスコード400で却下されます。 1. トピック値が正規表現パターン( [a-zA-Z0-9-_.~%]{1,100})を満たしていない。2. トピックにアクティブなサブスクリプションがない。つまり、このトピックをサブスクライブしている registrationIdがない。3. 以下のような、トピック以外の入力パラメーターが無効である。 - InvalidData- データも通知フィールドも指定されていない。- InvalidConsolidationKey- キーの長さが64文字を超えている。- InvalidExpiration- 値が1未満である、または2678400を超えている。- InvalidType- ヘッダーに指定されたtypeVersionとacceptTypeの値が使用可能な値ではない。4. accessTokenで識別されるセキュリティプロファイルがTopicBasedMessagingに登録されていない。 | "reason": "Security profile amzn1.application.<32個の16進文字> is not registered for TopicBasedMessaging." | 
| 401 | 提供されたアクセストークンが無効です。送信元は、アクセストークンを更新する必要があります。更新の手順については、アクセストークンのリクエスト方法を参照してください。 | "reason":"AccessTokenExpired" | 
| 413 | dataパラメーターで渡されたメッセージペイロードが、許容されている最大データサイズ(6KB)を超えています。 | "reason":"MessageTooLarge" | 
| 429 | リクエスト側が、許容されている最大メッセージレートを超過しました。送信元は、レスポンスに含まれている Retry-Afterヘッダーの指示に従って、後で再試行できます。ADMでは、一定時間内に送信されるメッセージ数を制限することで、高可用性が確保されています。具体的なキャパシティ要件がある場合は、お問い合わせから以下の情報をお知らせください。- 氏名 - 会社名 - Eメールアドレス - 必要なTPS(1秒あたりのトランザクション数)制限 - 理由 | "reason":"MaxRateExceeded" | 
| 500 | 内部サーバーエラーが発生しました。リクエスト側は、レスポンスに含まれている Retry-Afterヘッダーの指示に従って、後で再試行できます。 | なし | 
| 503 | サーバーが一時的に利用できない状態になっています。リクエスト側は、レスポンスに含まれている Retry-Afterヘッダーの指示に従って、後で再試行できます。 | なし | 
レスポンスヘッダーフィールド
| フィールド | 概要 | 例 | 
|---|---|---|
| X-Amzn-RequestId | リクエストを一意に識別するためにADMによって作成された値。万が一、ADMに問題が発生した場合は、Amazon側でこの値を用いてトラブルシューティングを行います。 | X-Amzn-RequestId: <Amazon RequestId> | 
| Retry-After | エラーレスポンス429、500、503が発生した場合に、このフィールドが返されます。Retry-Afterメッセージは、サービスが利用できない時間の長さ(予測)を示します。有効値は、レスポンス後の10進法の秒数またはHTTP形式の日付です。この値の有効な形式については、HTTP/1.1仕様のセクション10.2.3を参照してください。 | Retry-After: 30 | 
| Content-Type | リソースのコンテンツタイプ: application/json | Content-Type: application/json | 
トピックへのメッセージの送信とレスポンスの処理
以下のコードサンプルは、サーバーソフトウェアでトピックにメッセージを送信し、ADMサーバーのレスポンスを処理する方法を示します。
/**
 * 特定のトピックをサブスクライブしているアプリインスタンスにメッセージを配信するようADMにリクエストします。
 */
public void sendMessageToTopic(String topic, String accessToken) throws Exception
{
    // メッセージのJSONペイロード表現。
    JSONObject payload = new JSONObject();
    // データメッセージ内容のキーと値のペアを定義し、メッセージペイロードに
    // 追加します。
    JSONObject data = new JSONObject();
    data.put("firstKey", "firstValue");
    data.put("secondKey", "secondValue");
    payload.put("data", data);
    
    // 通知メッセージ内容のキーと値のペアを定義し、メッセージペイロードに
    // 追加します。
    // 通知メッセージでは、事前に定義されたキーと値のペアのみを使用できます。
    // ADM通知でサポートされているキーと値のペアについては、ドキュメント
    //(https://developer.amazon.com/ja/docs/adm/message-types.html#notification)を参照してください。
    JSONObject notification = new JSONObject();
    notification.put("title", "ADM Message Notification");
    notification.put("body", "We have a new offer for you!");
    payload.put("notification", notification);
    // 連結キーを追加します。アプリインスタンスに対して、同じ連結キーを持つ
    // 複数のメッセージの配信が保留になっている場合、ADMでは最後に追加されたメッセージの
    // 配信が試行されます。
    payload.put("consolidationKey", "ADM_Enqueue_Sample");
    // メッセージに、expiresAfter値(1日間)を追加します。ターゲットのアプリインスタンスが
    // expiresAfter期間内にオンラインにならなかった場合、メッセージは配信されません。
    payload.put("expiresAfter", 86400);
    // メッセージにpriorityを追加します。priorityの値によって、デバイスがDoze/アイドルモードの場合に
    // メッセージが配信されるかどうかが決まります。
    payload.put("priority", "high");
    
    // メッセージ本文にトピックを追加します。これを使用してメッセージをアプリインスタンスに配信します。
    payload.put("topic", topic);
    // メッセージをJSONオブジェクトから文字列に変換します。
    String payloadString = payload.toString();
    // ベースURLを設定します
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/messages";
    URL admUrl = new URL(admUrlTemplate);
    // POSTリクエスト用のHTTPS接続を生成します。HTTP接続は
    // 行えません。
    HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
    conn.setRequestMethod("POST");
    conn.setDoOutput(true);
    // コンテンツタイプを設定し、ヘッダーを受け取ります。
    conn.setRequestProperty("content-type", "application/json");
    conn.setRequestProperty("accept", "application/json");
    conn.setRequestProperty("X-Amzn-Type-Version", "com.amazon.device.messaging.ADMMessage@1.0");
    conn.setRequestProperty("X-Amzn-Accept-Type", "com.amazon.device.messaging.ADMSendResult@1.0");
    // ヘッダーとして認証トークンを追加します。
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);
    // 接続の出力ストリームを取得し、メッセージペイロードを書き込みます。
    OutputStream os = conn.getOutputStream();
    os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
    os.flush();
    conn.connect();
    // 接続からレスポンスコードを取得します。
    int responseCode = conn.getResponseCode();
    // 失敗のレスポンスを受信したかどうか確認し、受信した場合は失敗の理由を取得します。
    if( responseCode != 200)
    {
        if( responseCode == 401 )
        {
            // レスポンスコード401を受信した場合は、アクセストークンの期限が切れていることを意味します。トークンを更新してください。
            // このリクエストは再試行できます。
        }
        String errorContent = parseResponse(conn.getErrorStream());
        throw new RuntimeException(String.format("ERROR: The send message to the topic %s request failed with a " +
                                         "%d response code, with the following message: %s",
                                         topic, responseCode, errorContent));
    }
    else
    {
        // リクエストに成功しました。レスポンスに、送信メッセージと 
        // その送信先のトピックに関連付けられたmessageIdが記載されます。
        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);
        String messageId = parsedObject.getString("messageId");
        // メッセージがアプリインスタンスに配信されない場合、messageIdをADMに通知して
        // 問題のトラブルシューティングを行えます。
}
private String parseResponse(InputStream in) throws Exception
{
    // 入力ストリームから読み取り、文字列に変換します。
    InputStreamReader inputStream = new InputStreamReader(in);
    BufferedReader buff = new BufferedReader(inputStream);
    StringBuilder sb = new StringBuilder();
    String line = buff.readLine();
    while(line != null)
   {
      sb.append(line);
      line = buff.readLine();
    }
    return sb.toString();
}

