[Salesforce]外部システムからからファイルを取得/ダウンロード


この記事は「[Salesforce Platform Advent Calendar 2020](https://qiita.com/advent-calendar/2020/salesforce-platform)」の第16日目の記事です。

また、この記事は[Salesforce 開発者向けブログ投稿キャンペーン](https://developer.salesforce.com/jpblogs/2020/11/salesforce-developers-blog-4th/)へのエントリー記事です。

#Salesforce外部からのファイルダウンロードに関して
　今回は、Salesforce外部のシステムからのAPI連携で画像ファイルなどを取得する処理を実装する必要があり、実装に少し困ったのでその方法を書いておきます。
　結論、カスタムで画面を開発して、JavaScriptを使用すればローカルPCへファイルのダウンロードが可能です。今回はVisualforceで実装してみました。また、今回は実際にAPIコールは行わず、静的リソースからバイナリーデータを取得して、疑似的にHttpResponseの値を取得した形にしております。

#実際のコード
```FileDownload.vfp
<apex:page controller="FileDownloadCtrl">
  <apex:form>
    <apex:pageMessages id="msg" />

    <apex:outputPanel layout="block" style="margin-top: 1rem; margin-left: 1rem;">
      <apex:inputText value="{!fileName}" />
      <apex:commandButton value="ファイルダウンロード" action="{!doDownload}" reRender="msg" oncomplete="doDownload('{!fileName}', '{!fileData}', '{!mimeType}');"
      />
    </apex:outputPanel>
  </apex:form>

  <script>
    function doDownload(fileName, fileData, mimeType) {

      // データがないなら処理を実行しない
      if (!fileData) {
        return;
      }

      // Blobデータの作成
      var bin = atob(fileData);
      var buffer = new Uint8Array(bin.length);
      for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
      }
      var blob = new Blob([buffer.buffer], { type: mimeType });

      // ダウンロードを実行
      var link = document.createElement("a");
      link.download = fileName;
      link.href = window.URL.createObjectURL(blob);
      link.click();
      URL.revokeObjectURL(blob);
    }
  </script>
</apex:page>
```

```Apex:FileDownloadCtrl.cls
public with sharing class FileDownloadCtrl {
    // コンストラクタ
    public FileDownloadCtrl() {
    }

    // file名
    public String fileName {get; set;}

    // fileデータ
    Transient public String fileData {get; set;}

    // MIMETYPE
    public String mimeType {get; set;}

    // ファイルダウンロード実行
    public void doDownload() {
        List<StaticResource> srList = [SELECT Id, Body, ContentType FROM StaticResource WHERE Name = :fileName];

        if (srList.isEmpty()) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, '該当ファイルがありません'));
        } else {
            this.fileData = EncodingUtil.base64Encode(srList[0].Body);                  // ファイルのデータ
            this.mimeType = srList[0].ContentType;                                      // MIMRタイプ
            this.fileName += FILE_EXTENTION_BY_CONTENT_TYPE.get(srList[0].ContentType); // ファイル名に拡張子をつける
        }

    }

    public FINAL Map<String, String> FILE_EXTENTION_BY_CONTENT_TYPE = new MAP<STring, String>{
        'image/png' => '.png'
        // .jpg, .pdf, .xls, .docなど
    };
}

```

#コードの解説
１．
「ファイルダウンロード」時にダウンロードしたいファイル名を取得し、一致する静的リソースのファイルを取得してきます。
（HttpReqest req = new Httpreqest();としてヘッダーやエンドポイントの設定をします。）

2.
取得してきた静的リソースのBodyをbase64エンコードして、Stringとして変数に保持します。
また、MIMEタイプもStringとして変数に保持します。
（実際には HttpResponseのgetBodyasBlob()メソッドを使用して、Blob型の変数に保持します。）

3.
上記の処理完了後にoncompleteでJavaScriptのメソッドを実行します。
ここで、base64文字列とMIMEタイプを使用して、Blobオブジェクトを作成します。
そのBlobオブジェクトを利用して、createObjectURLメソッドとlinkのダウンロード属性を利用して、クリック時にファイルをダウンロードするようにし、JavaScriptでクリック処理を実行させます。

このあたりの処理の詳細は、下記のサイトを参考にしてください。
[Blob-Web API](https://developer.mozilla.org/ja/docs/Web/API/Blob)
[Base64 のエンコードとデコード](https://developer.mozilla.org/ja/docs/Web/API/WindowBase64/Base64_encoding_and_decoding)
[ a: アンカー要素](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a)

#今回、はまって勉強したところ
①MIMEタイプの判定
　今回、使用するAPIではどのようなファイルでもContentTypeがapplication/octet-streamでレスポンスが返ってくるため、ファイルのMIMEタイプを判定することができないというところで少し悩みました。
　結論としては、画像ファイルなどはバイナリデータのヘッダー署名から、そのファイルがjpgなのか、pdfなのか、pdfなのか判断できることが分かりました。その一方で、office関連のxlsやdocファイルなどは、バイナリデータのヘッダー署名からファイルの実態を判断することは難しいということが分かりました。

　下記のサイトで様々なファイルのヘッダー署名を確認することが出来ますが、xlsファイルとdocファイルや、xlsxファイルとdocxファイルは署名が一致しており、その署名からファイルの拡張子を判断することが出来ませんでした。
[File Signature Database](https://www.filesignatures.net/index.php?page=search)

今回は、レスポンスのヘッダーにファイル登録時のファイル名が設定されて返ってくる仕様だったため、officeファイルはそのファイル名の拡張子からMIMEタイプを判断することとしました。
　
②IEでのファイルダウンロード
　IEではHTMLのa要素のdownload属性がサポートされていないため、上記のコードではダウンロードが出来ませんでした。こちらに関しては、window.navigater.msSaveBlob()メソッドを使用して、ダウンロードすることが出来ました。

[msSaveBlob](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/msSaveBlob)

#終わりに
　今回の実装が必要になるケースはそこまで多くはないと思いますが、この実装を行ったおかげでHttp通信やバイナリーデータに関する知識がかなり深まりました。この知識を活かして、今後新たなSalesforceハックに挑戦してみたいと思いました。