ApexトリガからGoogleDriveAPIをコールアウト

## 目的

Salesforceには、標準で[Salesforce Files](https://help.salesforce.com/articleView?id=collab_salesforce_files_parent.htm&type=5)というファイル管理機能がありますが、

- それとは別にファイル管理関連の[AppExchange](http://appexchangejp.salesforce.com/)を導入している
- 他のファイルサービスを併用している

という声を聞くことが多い気がします。（あくまでも主観ですが）

他のファイルサービスとの連携に関しては、標準で[Salesforce Files Connect](https://help.salesforce.com/articleView?id=collab_admin_files_connect.htm&type=5)という機能があり、主要なファイルサービスとの連携は、ほぼほぼこの機能の範疇で出来てしまいます。ただ、基本的には、連携したファイルサービス側のファイルをSalesforceからダウンロードしたり、逆にSalesforceからアップロードしたり、一覧表示できたり、程度のオーソドックスな機能のみとなっています。

なので、もう少し込み入った事をしたい場合、各ファイルサービスが公開しているAPIを使用する形になると思いますが、直近でそのような要望をいただく事があったので、今回、GoogleDriveAPIを題材としてAPI連携時に必要な作業を備忘録的に記載しておきます。

## 前提

- Googleアカウント取得済み
- Salesforceアカウント取得済み
- GoogleDriveAPI v3

## 手順

### 1. Google側の設定

#### 1. プロジェクト作成 

[Google Developer Console](https://console.developers.google.com/)にアクセスし、Googleアカウントでログイン後、`プロジェクトを作成`をクリックします。
<img width="1440" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/821ebed7-39e0-d868-4aa5-1680a7939597.png">

プロジェクト名を入力し、`作成`をクリックします。
<img width="1440" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/3d6d7f06-87ae-db22-bf44-179474ef2127.png">

#### 2. GoogleDriveAPIの有効化

作成したプロジェクトの`ダッシュボード`から以下の内容を入力し、`APIとサービスを有効化`をクリックします。
<img width="1440" alt="3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/fc20645c-f11a-fc41-ea26-332396cfba69.png">

`有効にする`をクリックします。
<img width="794" alt="4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/6f9e2a1b-f4c8-168d-d5d9-a661f955d772.png">

#### 3. OAuth同意画面の設定

`OAuth同意画面`から以下の内容を入力し、`保存`をクリックします。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/5f44d34c-851f-1a27-2929-fa3edeadc7a3.png)

| 項目 | 値 |
|:--|:--|
| アプリケーション名 | 任意 |
| サポートメール | あなたのメールアドレス |
| 承認済みドメイン | Salesforce.com |

#### 4. 認証情報作成

`認証情報`から`認証情報を作成`をクリックし、`OAuthクライアントID`を選択します。
<img width="1440" alt="6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/873ceeed-009e-06b4-8de1-ce21ba817864.png">

以下の内容を入力し、`作成`をクリックします。
<img width="1440" alt="7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/92f6381c-8077-92bb-a203-b534108cbe4d.png">

| 項目 | 値 |
|:--|:--|
| アプリケーションの種類 | ウェブアプリケーション |
| 名前 | 任意 |

作成されたクライアントの詳細から`JSONをダウンロード`をクリックします。
<img width="1440" alt="8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/14868e4e-e568-da2f-abbc-4f40ec01705a.png">

ダウンロードされたファイルを開くと、クライアントIDを始め、認証エンドポイントやトークンエンドポイントの情報が記載されているので、これらの情報を元にSalesforce側の設定を行います。

```json:client_secret_〜.json
{
    "web": {
        "client_id": [client_id],
        "project_id": "salesforceconnect-258102",
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
        "client_secret": [client_secret]
    }
}
```

### 2. Salesforce側の設定

#### 1. 認証プロバイダ作成

設定 > 認証プロバイダから`新規`をクリックします。
<img width="1440" alt="9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/23f020d1-07a4-15da-078d-eb500651461f.png">

以下の内容を入力し、`保存`をクリックします。
<img width="1440" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/fa2552f5-7fdc-e15f-c0fe-7b3d8cf87f90.png">

| 項目 | 値 |
|:--|:--|
| プロバイダタイプ | Open ID Connect |
| 名前 | 任意 |
| URL 接尾辞 | 任意 |
| コンシューマ鍵 | Google認証情報JSONのclient_id |
| コンシューマの秘密 | Google認証情報JSONのclient_secret |
| 承認エンドポイント URL | Google認証情報JSONのauth_uri |
| トークンエンドポイント URL | Google認証情報JSONのtoken_uri |

#### 2. GoogleプロジェクトのコールバックURL更新

認証プロバイダを作成すると、Googleアカウントで認証された後の遷移先となるコールバックURLが払い出されます。
<img width="1440" alt="11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/01adea53-808c-187a-e99b-8df0b0cf55a2.png">

これを[手順1-4.](#4-認証情報作成)で作成した認証情報の`承認済みのリダイレクトURI`に貼付し、`保存`をクリックします。
<img width="1440" alt="12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/2bfbe4b5-f575-bdf5-9982-c1a22b8e0716.png">

#### 3. 指定ログイン情報を作成

Apexからコールアウトする際は、通常リモートサイトの設定をしなければいけませんが、[指定ログイン情報](https://help.salesforce.com/articleView?id=named_credentials_define.htm&type=5)を登録することでこれをスキップできます。また、指定ログイン情報に、[手順2-1.](#1-認証プロバイダ作成)で作成した認証プロバイダの設定とエンドポイント、スコープを登録しておくことで、プログラムでのアクセストークン取得〜対象APIへのリクエスト作成部分を省略することができます。

設定 > 指定ログイン情報から`新規指定ログイン情報`をクリックします。
<img width="1440" alt="13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/d5bf99d2-8635-34e0-b422-a5e8037a6598.png">

以下の内容を入力し、`保存`をクリックします。
<img width="1440" alt="14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/e5201745-d6ed-2053-39db-51eecd00920c.png">

| 項目 | 値 |
|:--|:--|
| 表示ラベル | 任意 |
| 名前 | 任意 |
| URL | https://www.googleapis.com/drive/v3/files |
| ID 種別 | 指定ユーザ |
| 認証プロトコル | OAuth 2.0 |
| 認証プロバイダ | [手順2-1.](#1-認証プロバイダ作成)で作成した認証プロバイダ |
| 範囲 | https://www.googleapis.com/auth/drive |
| 保存時に認証フローを開始 | true |

※URLと範囲は、[GoogleDriveAPI v3のリファレンス](https://developers.google.com/drive/api/v3/reference/files/create?apix_params=%7B%22resource%22%3A%7B%22mimeType%22%3A%22application%2Fvnd.google-apps.folder%22%2C%22name%22%3A%22test%22%7D%7D)記載の内容を指定する

認証が完了すると、認証状況が`認証済み`に変わります。
<img width="1440" alt="16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/7bbc82c2-f810-fe0f-3c7f-fbb00fadd599.png">

#### 4. Apexトリガにコールアウト処理を実装

非常にシンプルですが、取引先作成時に取引先名のフォルダーをGoogleDriveのマイドライブに作成する処理を実装します。
※ガバナ制限等、全く考慮していません

```java:AccountTrigger.trigger
trigger AccountTrigger on Account(after insert) {
    AccountTriggerHandler handler = new AccountTriggerHandler();
    if (Trigger.isInsert && Trigger.isAfter) {
        handler.onAfterInsert(Trigger.newMap);
    }
}
```

```java:AccountTriggerHandler.cls
public with sharing class AccountTriggerHandler {

    public void onAfterInsert(Map<Id, Account> newAccMap) {
        String targetAccName = '';
        for(Account acc : newAccMap.values()){
            targetAccName = acc.Name;
        }
        createGoogleDriveFolder(targetAccName);
    }

    @Future(callout=true)
    public static void createGoogleDriveFolder(String folderName) {

        Http http = new Http();
        HttpRequest req = new HttpRequest();
        
        // 指定ログイン情報を使用
        String reqPath = 'callout:google_driveapi';
        req.setEndpoint(reqPath);
        req.setMethod('POST');
        req.setHeader('content-type', 'application/json');
        JSONGenerator gen = JSON.createGenerator(true);
        gen.writeStartObject();
        // フォルダーに指定されるmimeTypeを指定
        gen.writeStringField('mimeType', 'application/vnd.google-apps.folder');
        // フォルダー名を指定
        gen.writeStringField('name', folderName);
        gen.writeEndObject();
        String reqBody = gen.getAsString();
        req.setBody(reqBody);
        
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            System.debug(results);
        }

    }
}
```

取引先を作成します。
<img width="1440" alt="17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/180cb7dd-39b0-ba74-81e1-049109b82232.png">
<img width="1440" alt="18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/b1dd43d3-032a-a30d-9899-546c6a49e04a.png">

GoogleDriveに取引先名のフォルダが作成されていることを確認します。
<img width="1440" alt="19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261260/a1b570ca-83dd-5c54-1a08-a4f55bcae0ed.png">

## まとめ

サービス連携という側面では、GoogleDrive側の公開範囲設定と、Salesforce側のロジック（どこにフォルダを作成するのか）で足並みを揃えれば、公開範囲等も、ある程度柔軟にコントロールすることができそうだし、
GoogleDriveのローカル同期を可能にする[ドライブ ファイル ストリーム](https://support.google.com/a/answer/7491144?hl=ja)を併用することでGoogleDrive⇔Salsforce⇔ローカルが繋がるので、やり方次第では非常に効率の良い作業環境が作れるのでは、と思いました。

技術的な側面では、今回、認証設定とエンドポイントを一纏めに定義する`指定ログイン情報`というものを使い、ロジックを簡略化できたのが良かったのですが（今まで普通にURL組み立ててた）、画面に表示されているようにアクセストークンの有効期限切れに関するWarningが出力されました。

> 認証プロバイダが更新トークンを提供しませんでした。アクセストークンの有効期限が切れると、組織からこの指定ログイン情報にアクセスできなくなります。

これが設定漏れ／誤りの影響ではない場合、定期的にこの指定ログイン情報を再保存（認証フローを再度行う）必要があるので、どうするのが正解なのか疑問が残りました。

## 参考サイト

https://web.plus-idea.net/2017/01/salesforce-google-calendar-rest/
https://himakan.net/websites/how_to_google_oauth