Amazon Product Advertising API 5.0 (PA-API v5) の SDK (paapi5-java-sdk-1.0.0) を使う 

## 概要

- Java から Amazon Product Advertising API 5.0 (PA-API v5) を使用する
- API を直接コールせず AWS が提供している公式の SDK (paapi5-java-sdk-1.0.0) を使用する
- 今回の実行環境: macOS Catalina + Java 8 + Gradle 6.1

## paapi5-java-sdk-1.0.0 のダウンロード

paapi5-java-sdk-1.0.0 は Maven Central Repository などで配布されていないため、 jar ファイルをダウンロードして使う。

[Using SDK · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/quick-start/using-sdk.html) から paapi5-java-sdk-example.zip をダウンロードして展開する。

sdk ディレクトリには paapi5-java-sdk-1.0.0 を使用するサンプルコードが入っている。

```
$ ls ./sdk/ | sort
Callback.java
ConnectionPoolSampleRequest.java
GetBrowseNodes.java
GetBrowseNodesAsync.java
GetBrowseNodesWithHttpInfo.java
GetItems.java
GetItemsAsync.java
GetItemsWithHttpInfo.java
GetVariations.java
GetVariationsAsync.java
GetVariationsWithHttpInfo.java
SearchItems.java
SearchItemsAsync.java
SearchItemsWithHttpInfo.java
```

dependencies ディレクトリには SDK である paapi5-java-sdk-1.0.0.jar とその依存ライブラリの jar ファイルが入っている。

```
$ ls ./dependencies/ | sort
commons-codec-1.11.jar
commons-logging-1.2.jar
gson-2.8.1.jar
gson-fire-1.8.0.jar
hamcrest-core-1.3.jar
httpclient-4.5.5.jar
httpcore-4.4.9.jar
jackson-annotations-2.9.0.jar
jackson-core-2.9.5.jar
jackson-core-asl-1.9.2.jar
jackson-databind-2.9.3.jar
jackson-mapper-asl-1.9.2.jar
jackson-xc-1.9.2.jar
jackson-xml-databind-0.6.2.jar
json-20180130.jar
junit-4.12.jar
logging-interceptor-2.7.5.jar
okhttp-2.7.5.jar
okio-1.6.0.jar
paapi5-java-sdk-1.0.0.jar
stax-api-1.0-2.jar
stax2-api-3.1.0.jar
swagger-annotations-1.5.15.jar
threetenbp-1.3.5.jar
```

paapi5-java-sdk-1.0.0.jar ファイルを展開して pom.xml を見ると依存情報が載っているので、依存ライブラリについての設定はこれを参考にする。

以下に META-INF/maven/com.amazon.paapi5/paapi5-java-sdk/pom.xml より依存情報を一部抜粋する。

```xml
<dependencies>
    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-annotations</artifactId>
        <version>${swagger-core-version}</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.okhttp</groupId>
        <artifactId>okhttp</artifactId>
        <version>${okhttp-version}</version>
    </dependency>
    <dependency>
```

## paapi5-java-sdk-1.0.0 を使うサンプルコード

### ファイル一覧

今回はダウンロードした paapi5-java-sdk-1.0.0.jar を libs ディレクトリ以下に置く。

```
├── build.gradle
├── libs
│   └── paapi5-java-sdk-1.0.0.jar
├── settings.gradle
└── src
    └── main
        └── java
            └── com
                └── example
                    └── App.java
```

### build.gradle

Gradle の設定ファイル。

```groovy
plugins {
  id 'java'
  id 'application'
}

repositories {
  mavenCentral()
}

dependencies {

  // ダウンロードした paapi5-java-sdk
  implementation files('libs/paapi5-java-sdk-1.0.0.jar')

  // paapi5-java-sdk が依存しているライブラリ (paapi5-java-sdk-1.0.0.jar の pom.xml を参考にした)
  implementation 'io.swagger:swagger-annotations:1.5.15'
  implementation 'com.squareup.okhttp:okhttp:2.7.5'
  implementation 'com.squareup.okhttp:logging-interceptor:2.7.5'
  implementation 'com.google.code.gson:gson:2.8.1'
  implementation 'io.gsonfire:gson-fire:1.8.0'
  implementation 'org.threeten:threetenbp:1.3.5'
}

application {
  mainClassName = 'com.example.App'
}

sourceCompatibility = targetCompatibility = '1.8'
```

### settings.gradle

Gradle の設定ファイル。

```
rootProject.name = 'hello-amazon-pa-api-world'
```

### App.java

paapi5-java-sdk-1.0.0 を使うサンプルプログラム。

```java
package com.example;

import com.amazon.paapi5.v1.ApiClient;
import com.amazon.paapi5.v1.ApiException;
import com.amazon.paapi5.v1.ByLineInfo;
import com.amazon.paapi5.v1.Contributor;
import com.amazon.paapi5.v1.ErrorData;
import com.amazon.paapi5.v1.GetItemsRequest;
import com.amazon.paapi5.v1.GetItemsResource;
import com.amazon.paapi5.v1.GetItemsResponse;
import com.amazon.paapi5.v1.Item;
import com.amazon.paapi5.v1.PartnerType;
import com.amazon.paapi5.v1.SearchItemsRequest;
import com.amazon.paapi5.v1.SearchItemsResource;
import com.amazon.paapi5.v1.SearchItemsResponse;
import com.amazon.paapi5.v1.api.DefaultApi;

import java.util.ArrayList;
import java.util.List;

public class App {

  public static void main(String[] args) {

    ApiClient client = new ApiClient();
    client.setAwsAccessKey("<YOUR-ACCESS-KEY-HERE>"); // 取得したアクセスキー
    client.setAwsSecretKey("<YOUR-SECRET-KEY-HERE>"); // 取得したシークレットキー
    client.setHost("webservices.amazon.co.jp"); // Amazon.co.jp の Web API ホスト
    client.setRegion("us-west-2"); // Amazon.co.jp では us-west-2 を指定

    String trackingId = "<YOUR-PARTNER-TAG-HERE>"; // 取得したトラッキングID

    // キーワードから商品を検索
    System.out.println("===== searchItems ================================");
    String keywords = "シェイクスピア";
    searchItems(client, trackingId, keywords);

    // ASIN から商品情報を取得
    System.out.println("===== getItems ===================================");
    List<String> itemIds = new ArrayList<>();
    itemIds.add("4391641585");
    itemIds.add("B010EB1HR4");
    itemIds.add("B0125SPF90");
    itemIds.add("B07V52KSGT");
    getItems(client, trackingId, itemIds);
  }

  // キーワードから商品を検索
  public static void searchItems(ApiClient client, String trackingId, String keywords) {

    // レスポンスに含む値のタイプを指定
    // https://webservices.amazon.com/paapi5/documentation/search-items.html#resources-parameter
    List<SearchItemsResource> searchItemsResources = new ArrayList<SearchItemsResource>();
    searchItemsResources.add(SearchItemsResource.ITEMINFO_TITLE);
    searchItemsResources.add(SearchItemsResource.ITEMINFO_PRODUCTINFO);
    searchItemsResources.add(SearchItemsResource.IMAGES_PRIMARY_MEDIUM);
    searchItemsResources.add(SearchItemsResource.OFFERS_LISTINGS_PRICE);

    // リクエスト情報を組み立てる
    SearchItemsRequest req = new SearchItemsRequest()
      .itemCount(5) // 検索結果の数
      .partnerTag(trackingId) // トラッキングID
      .keywords(keywords) // 検索キーワード
      .searchIndex("All") // 検索カテゴリー (AmazonVideo, Books, Hobbies, Music などを指定可能)
      .resources(searchItemsResources) // レスポンスに含む値のタイプ
      .partnerType(PartnerType.ASSOCIATES);

    try {
      // 検索を実行
      DefaultApi api = new DefaultApi(client);
      SearchItemsResponse res = api.searchItems(req);

      // 結果を取得
      if (res.getSearchResult() != null) {
        for (Item item : res.getSearchResult().getItems()) {
          System.out.println("--------------------------------------------------");
          // タイトル, ASIN, 商品URL
          System.out.println("Title: " + item.getItemInfo().getTitle().getDisplayValue());
          System.out.println("ASIN: " + item.getASIN());
          System.out.println("DetailPageURL: " + item.getDetailPageURL());
          // アダルト商品かどうか
          System.out.println("IsAdultProduct: " + item.getItemInfo().getProductInfo().getIsAdultProduct().isDisplayValue());
          // 画像URL
          System.out.println("ImageURL: " + item.getImages().getPrimary().getMedium().getURL());
          // 価格
          if (item.getOffers() != null
            && item.getOffers().getListings() != null
            && item.getOffers().getListings().get(0).getPrice() != null
            && item.getOffers().getListings().get(0).getPrice().getDisplayAmount() != null) {
            System.out.println("Price: " + item.getOffers().getListings().get(0).getPrice().getDisplayAmount());
          }
        }
      }
      // エラー処理
      if (res.getErrors() != null) {
        for (ErrorData error : res.getErrors()) {
          System.out.println("Error code: " + error.getCode());
          System.out.println("Error message: " + error.getMessage());
        }
      }
    } catch (ApiException e) {
      // エラー処理
      e.printStackTrace();
      System.out.println("Status code: " + e.getCode());
      System.out.println("Errors: " + e.getResponseBody());
      System.out.println("Message: " + e.getMessage());
      if (e.getResponseHeaders() != null) {
        System.out.println("Request ID: " + e.getResponseHeaders().get("x-amzn-RequestId"));
      }
    } catch (Exception e) {
      // エラー処理
      e.printStackTrace();
    }
  }

  // ASIN から商品情報を取得
  public static void getItems(ApiClient client, String trackingId, List<String> asinList) {

    // レスポンスに含む値のタイプを指定
    // https://webservices.amazon.com/paapi5/documentation/get-items.html#resources-parameter
    List<GetItemsResource> getItemsResources = new ArrayList<GetItemsResource>();
    getItemsResources.add(GetItemsResource.ITEMINFO_TITLE);
    getItemsResources.add(GetItemsResource.ITEMINFO_BYLINEINFO);

    // リクエスト情報を組み立てる
    GetItemsRequest req = new GetItemsRequest()
      .itemIds(asinList) // ASIN のリスト
      .partnerTag(trackingId) // トラッキングID
      .resources(getItemsResources) // レスポンスに含む値のタイプ
      .partnerType(PartnerType.ASSOCIATES);

    try {
      // 検索を実行
      DefaultApi api = new DefaultApi(client);
      GetItemsResponse res = api.getItems(req);

      // 結果を取得
      if (res.getItemsResult() != null) {
        for (Item item : res.getItemsResult().getItems()) {
          System.out.println("--------------------------------------------------");
          // タイトル, ASIN, 商品URL
          System.out.println("Title: " + item.getItemInfo().getTitle().getDisplayValue());
          System.out.println("ASIN: " + item.getASIN());
          System.out.println("DetailPageURL: " + item.getDetailPageURL());
          // 著者などの貢献者情報
          ByLineInfo bi = item.getItemInfo().getByLineInfo();
          if (bi != null && bi.getContributors() != null) {
            for (Contributor c : bi.getContributors()) {
              System.out.println("Contributor: " + c.getRole() + ": " + c.getName());
            }
          }
        }
      }
      // エラー処理
      if (res.getErrors() != null) {
        for (ErrorData error : res.getErrors()) {
          System.out.println("Error code: " + error.getCode());
          System.out.println("Error message: " + error.getMessage());
        }
      }
    } catch (ApiException e) {
      // エラー処理
      e.printStackTrace();
      System.out.println("Status code: " + e.getCode());
      System.out.println("Errors: " + e.getResponseBody());
      System.out.println("Message: " + e.getMessage());
      if (e.getResponseHeaders() != null) {
        System.out.println("Request ID: " + e.getResponseHeaders().get("x-amzn-RequestId"));
      }
    } catch (Exception e) {
      // エラー処理
      e.printStackTrace();
    }
  }
}
```

## サンプルコードの実行結果

実行環境: macOS Catalina + Gradle 6.1 + Java 8 (AdoptOpenJDK 1.8.0_242-b08)

```
$ gradle run

> Task :run
===== searchItems ================================
--------------------------------------------------
Title: ７人のシェイクスピア　ＮＯＮ　ＳＡＮＺ　ＤＲＯＩＣＴ（１１） (ヤングマガジンコミックス)
ASIN: B084BP9ZD9
DetailPageURL: https://www.amazon.co.jp/dp/B084BP9ZD9?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=osi&th=1&psc=1
IsAdultProduct: false
ImageURL: https://m.media-amazon.com/images/I/51KrT7qGuOL._SL160_.jpg
Price: ￥660
--------------------------------------------------
Title: シェイクスピア全集 (1) ハムレット　(ちくま文庫)
ASIN: 4480033017
DetailPageURL: https://www.amazon.co.jp/dp/4480033017?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=osi&th=1&psc=1
IsAdultProduct: false
ImageURL: https://m.media-amazon.com/images/I/51RZYRYGQ8L._SL160_.jpg
Price: ￥726
--------------------------------------------------
Title: ハムレット(字幕版)
ASIN: B015BY1Q6Q
DetailPageURL: https://www.amazon.co.jp/dp/B015BY1Q6Q?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=osi&th=1&psc=1
IsAdultProduct: false
ImageURL: https://m.media-amazon.com/images/I/516XD+o35gL._SL160_.jpg
Price: ￥400
--------------------------------------------------
Title: あらすじで読むシェイクスピア全作品 (祥伝社新書)
ASIN: B06ZZH149Y
DetailPageURL: https://www.amazon.co.jp/dp/B06ZZH149Y?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=osi&th=1&psc=1
IsAdultProduct: false
ImageURL: https://m.media-amazon.com/images/I/41Ms+C0NwNL._SL160_.jpg
Price: ￥836
--------------------------------------------------
Title: 第8話
ASIN: B07WPX7D76
DetailPageURL: https://www.amazon.co.jp/dp/B07WPX7D76?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=osi&th=1&psc=1
IsAdultProduct: false
ImageURL: https://m.media-amazon.com/images/I/517ol4X4BwL._SL160_.jpg
===== getItems ===================================
--------------------------------------------------
Title: すみっコぐらし検定公式ガイドブック すみっコぐらし大図鑑 (生活シリーズ)
ASIN: 4391641585
DetailPageURL: https://www.amazon.co.jp/dp/4391641585?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=ogi&th=1&psc=1
Contributor: 監修: サンエックス
Contributor: 編集: 主婦と生活社
--------------------------------------------------
Title: 守護神伝 第二章 <エクスパンデッド・エディション>(リマスター)
ASIN: B010EB1HR4
DetailPageURL: https://www.amazon.co.jp/dp/B010EB1HR4?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=ogi&th=1&psc=1
Contributor: アーティスト: ハロウィン
--------------------------------------------------
Title: ハロウィン ロッキング パンプキン ホームデコレーション用小物 H 130cm
ASIN: B0125SPF90
DetailPageURL: https://www.amazon.co.jp/dp/B0125SPF90?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=ogi&th=1&psc=1
--------------------------------------------------
Title: ハロウィン (字幕版)
ASIN: B07V52KSGT
DetailPageURL: https://www.amazon.co.jp/dp/B07V52KSGT?tag=<YOUR-PARTNER-TAG-HERE>&linkCode=ogi&th=1&psc=1
Contributor: 出演: ジェイミー・リー・カーティス
Contributor: 出演: ジュディ・グリア
Contributor: 出演: アンディ・マティチャック
Contributor: 出演: ウィル・パットン
Contributor: 出演: ヴァージニア・ガードナー
Contributor: 出演: ニック・キャッスル
Contributor: 出演: ジェームス・ジュード・コートニー
Contributor: 出演: ハルク・ビルギナー
Contributor: 出演: リアン・リース
Contributor: 出演: ジェファーソン・ホール
Contributor: 監督: デヴィッド・ゴードン・グリーン
Contributor: Writer: デヴィッド・ゴードン・グリーン
Contributor: Writer: ジェフ・フラッドリー
Contributor: Writer: ダニー・マクブライド
Contributor: プロデュース: マレク・アカッド
Contributor: プロデュース: ジェイソン・ブラム
Contributor: プロデュース: ビル・ブロック

BUILD SUCCESSFUL in 3s
2 actionable tasks: 2 executed
```

## 参考資料

- [PA-API v5移行ガイド - アソシエイト・セントラル](https://affiliate.amazon.co.jp/help/node/topic/GZH32YX29UH5GACM)
- [Introduction · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/)
    - [Using SDK · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/quick-start/using-sdk.html)
    - [Common Request Parameters · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html)
    - [SearchItems · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/search-items.html)
    - [GetItems · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/get-items.html)