Cloud IntegrationからOpen Connectors経由でGoogle Driveにファイルを格納する

## はじめに
[前回](https://qiita.com/tami/items/9fd12cbdeef81bed3984)の記事で、Open ConnectorsからGoogle Driveへの連携を設定しました。この記事では、Cloud Integration (CPI)からOpen Connectorsを呼び、Google Driveにファイルを格納します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7a3f8b5f-0f7f-b815-92d9-36e8f6ed2d5c.png)

「直接Open Connectorsを呼べばよくない？なぜCPIを挟む必要があるの？」と思う方もいるかもしれません。もちろん直接Open Connectorsを呼んでもいいのですが、私はCPIを使うと以下のメリットがあると考えています。

**認証情報を安全に渡せる**
Open Connectorsの認証情報は、`User xxxx, Organization xxxx, Element xxxx`の形式でヘッダに設定する必要があります。これはBasic認証やOAuth2.0などの一般的な認証方法と異なるため、BTPのDestinationで認証情報を設定することができません。このためアプリケーションからOpen Connectorsを呼ぼうとすると、ソースコードの中で認証情報を設定する必要がありますが、これは安全な方法ではありません。CPIはOAuth2.0による認証が可能なため、アプリケーションのコードで認証情報を設定する必要がありません。

**アプリケーション側でFormDataを扱わなくてよい**
Open ConnectorsからGoogle Driveへの連携では、ファイルをFormDataとして渡す必要があります。ロジックでFormDataを設定するのは、Bodyに直接ファイルをアップロードするのに比べて若干処理が複雑です。CPIでBodyに渡されたファイルをFormDataに変換してOpen Connectorsに渡すことで、アプリケーション側の処理を簡単にすることができます。

## ステップ
1. Open Connectors用の認証情報の登録
1. iFlowの作成
1. テスト

### 1. Open Connectors用の認証情報の登録
Security MaterialでOpen Connectors用の認証情報を登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5a3ae27b-41c7-2581-8453-abffea442d44.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/00014ea8-5ffc-24b3-fd45-ff3b2f658e9e.png)


### 2. iFlowの作成
作成したiFlowはこちらです。以下で各ステップの説明をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/362374ed-c76b-09d8-2ae8-af5ac25c8be3.png)

フローの設定として、ヘッダに"content-type"と"filename"を受け取れるようにしておきます。この設定がないと、呼び出し側でヘッダを指定してもフローの中に入ってきません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f1c4998c-1481-10cb-8f66-a5e93d38706b.png)

#### ①HTTPS Sender Adapter 
Addressに任意のパスを指定します。呼び出しを簡単にするため、CSRF Protectedのチェックは外しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0fd40230-001a-527d-6da0-b2c1512323dc.png)

#### ②認証情報の取得 [Groovy Script]
1. で登録したOpen Connectors用の認証情報を取得し、Authorizationヘッダに設定します。

```groovy
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

import com.sap.it.api.securestore.SecureStoreService;
import com.sap.it.api.securestore.UserCredential;
import com.sap.it.api.securestore.exception.SecureStoreException;
import com.sap.it.api.ITApiFactory;

def Message processData(Message message) {
    def secureStorageService =  ITApiFactory.getService(SecureStoreService.class, null)
    try{
        def secureParameter = secureStorageService.getUserCredential("OpenConnector_GoogleDrive")
        def credentials = secureParameter.getPassword()
        message.setHeader("Authorization", credentials)
    } catch(Exception e){
        throw new SecureStoreException("Secure Parameter not available")
    }    
    return message;
}
```

#### ③ヘッダの設定 [Content Modifier]
Open Connectorsに渡すためのヘッダを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bc3b5800-d351-9f50-d319-ebad4d4ab099.png)

#### ④Bodyの設定 [Groovy Script]
CPIには[OpenConnectors用のアダプター](https://1e40e2cb-b1df-4e5e.abap.eu10.hana.ondemand.com/sap/opu/odata/sap/ZRET_C_COMPWFSTATUS_API/?sap-client=100)が用意されていますが、残念ながらFormDataはサポートされていません。そこで、Groovy Scriptを使ってBodyに受け取ったファイルをFormDataとして設定し直します。スクリプトを書くにあたっては以下のQ&Aを参考にしました。

https://answers.sap.com/questions/13213564/sap-cpi-attachment-with-content-type-multipartform.html

```groovy
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

import javax.activation.DataHandler
import javax.mail.internet.ContentType
import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.mail.util.ByteArrayDataSource

def Message processData(Message message) {
    byte[] bytes = message.getBody(byte[])

    def map = message.getHeaders();
    def mimeType = map.get("content-type");
    def fileName = map.get("filename");
    
    //  Construct Multipart
    MimeBodyPart bodyPart = new MimeBodyPart()
    ByteArrayDataSource dataSource = new ByteArrayDataSource(bytes, mimeType)
    DataHandler byteDataHandler = new DataHandler(dataSource)
    bodyPart.setDataHandler(byteDataHandler)
    bodyPart.setFileName(fileName)
    bodyPart.setDisposition('form-data; name="file"')

    MimeMultipart multipart = new MimeMultipart()
    multipart.addBodyPart(bodyPart)

    // Set multipart into body
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
    multipart.writeTo(outputStream)
    message.setBody(outputStream)

    // Set Content type with boundary
    String boundary = (new ContentType(multipart.contentType)).getParameter('boundary');
    message.setHeader('Content-Type', "multipart/form-data; boundary=${boundary}")

    return message
}
```
#### ⑤Open Connectorsへ送信 [HTTPS Receiver Adapter]
Address、Query、Request Headersを指定します。Queryの`path`には"Openonector"というGoogleDriveに作成したフォルダ名と、ヘッダで受け取ったファイル名を結合して設定しています。`overwite=true`を指定することにより、同じファイルを繰り返しアップロードすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/0904c890-5887-a6ba-8a99-49059d9f6f92.png)

#### ⑥ヘッダを削除 [Content Modifier]
ここでは、ヘッダの"Authorization"を削除しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a5ae17d1-f37c-2fe9-ab1d-d8c9bdd26b2c.png)
なぜかというと、②で認証情報をGroovy Scriptで取得してヘッダに設定しているのですが、このヘッダが残ったままだとPostmanから実行したときに以下のようにエラーが返ってきてしまうためです（フロー自体は正常終了）。認証情報をContent Modifierでベタ書きした場合は起きなかったので、Groovy Scriptで取ってきた値が何らかの悪さをしていると思われますが原因は不明です。そこでワークアラウンドとして、最後にヘッダを削除することにしました。

```html
<html>

<body>
	<h1>502 Bad Gateway</h1>
	The server returned an invalid or incomplete response.
</body>

</html>
```

### 3. テスト
Postmanから以下のリクエストを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/4bf8d07f-c36b-a3a7-d793-e54c6e68b4bc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9e732a1f-dcb3-08a6-0c59-c61219720b89.png)
以下が返ってきた結果です。13秒ほどかかっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/18f0ffaf-5df9-4a06-ae08-8126592a9565.png)
Google Driveにファイルが格納されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7afe83a7-6d79-5e80-b3da-092daeab5fe9.png)

**比較**
同じリクエストをOpen Connectorに直接送ったときは7～8秒で返ってきました。CPIを挟むことにより、プラス5秒ほどかかったことになります。ユーザインターフェースから使う場合はパフォーマンスが重要になるので、CPIを挟まない方がいいかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c9b5ce3e-d43a-6d53-d507-e914b75821d5.png)