Salesforceからmaltipart形式でファイルデータをServiceNowに連携するMuleアプリケーションの紹介

Stay hungry. Stay foolish. Your time is limited, so don't waste it living someone else's life.

Salesforceからmaltipart形式でファイルデータをServiceNowに連携するMuleアプリケーションの紹介

はじめに

 SalesforceとServiceNow間でレコードやファイルをデータ連携する機会があり、今回は、SalesforceからマルチパートのリクエストをMuleに送信し、MuleからServiceNowにデータ連携するサンプルMuleアプリケーションを開発した。サンプルプログラムの利用手順について紹介する。
 Muleアプリケーションの処理の内容は以下の通り。

構築手順

利用手順は以下の通り。

  1. Mule:MuleアプリケーションをCloudHubに配置する。
  2. SFDC:指定ログイン情報を設定する。
  3. SFDC:リクエストモジュールを配置する。
  4. SFDC:開発者コンソールでリクエストを実行して、ServiceNowで結果を確認する。

手順詳細

1.Mule:MuleアプリケーションをCloudHubに配置する。

デプロイするMuleアプリケーションは、下記のGitHubリポジトリからダウンロードする。

GiTHub:サンプルソース

 Muleアプリケーションは、ServiceNowへBasic認証でログインするため、ServiceNowユーザのログイン情報を設定する。

servicenow.host= https://{instance-id}.service-now.com

servicenow.username= admin
servicenow.password= {password}

 CloudHubに配置したMuleアプリケーションとAPIインスタンスの紐づけを設定する。

2.SFDC:指定ログイン情報を設定する。

Apexクラスからコールアウトを実行できるように、指定ログイン情報を設定する。

3.SFDC:リクエストモジュールを配置する。

下記のプログラムをSalesforceに登録する。

public class Mule_ApplicationSender {
  
    private static String cId = '{Case Record ID}';
    private static String boundaryStr = '--202105151529';
    private static String endboundaryStr = '--' + boundaryStr +'--';

    @future (callout=true)
    public static void send() {

        Case cobj = getApplication(cId);
        List<ContentVersion> llist = findContentVersion(cobj); 
        String body = makeApplicationBody(cobj) + makeAttachedFilesBody(llist) + endboundaryStr;
        
        HttpRequest req = new HttpRequest();
        req.setTimeout(120000);
        req.setEndpoint('callout:mule_api/api/submit/1234');
        req.setMethod('POST');
        req.setHeader('X-Correlation-ID', getUUID());
        req.setHeader('jwt','{!$Credential.AuthorizationHeaderValue}');
        req.setHeader('Cache-Control', 'no-cache');
        req.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundaryStr);
        req.setHeader('Content-Length', String.valueOf(body.length()));
        req.setBody(body);
        System.debug(body);
        
        Http http = new Http();
        HTTPResponse res = http.send(req);
        System.debug(res.getStatus());
        System.debug(res.getStatusCode());
    }

    private static Case getApplication(String cId) {

        Case cobj = [SELECT Id,Subject,Status,Origin,Type,Reason,Description FROM Case WHERE Id = :cId];
        return cobj;
    }

    private static List<ContentVersion> findContentVersion(Case cobj) {

        List<ContentDocumentLink> llist = [SELECT ContentDocumentId, ContentDocument.title FROM ContentDocumentLink WHERE LinkedEntityId = :cobj.Id];
        List<String> dIdlist = new List<String>();
        for (ContentDocumentLink lobj : llist) {
            dIdlist.add(lobj.ContentDocumentId);
        }
        String dIds = '\'' + String.join(dIdlist,'\',\'') + '\'';
        String q= 'SELECT Id,Title,VersionData,FileType FROM ContentVersion WHERE ContentDocumentId IN (' + dIds + ')';
        List<ContentVersion> vlist = Database.query(q);
        System.debug(vlist);
        return vlist;
    }

    private static String makeApplicationBody(Case cobj) {

        String body = '--' + boundaryStr + '\r\n';
        body += 'Content-Disposition: form-data; name="application"\r\n';
        body += 'Content-Type: application/json\r\n\r\n';
        body += '{ "subject": "' + cobj.subject + '","status": "' + cobj.status + '","origin": "' + cobj.origin + '","type": "' + cobj.type + '","reason": "' + cobj.reason + '","description": "' + cobj.description + '" }\r\n';

        return body;
    }

    private static String makeAttachedFilesBody(List<ContentVersion> vlist) {
        String body = '';
        for(ContentVersion vobj : vlist){
            body += makeAttachedFileBody(vobj);
        }
        return body;
    }
    private static String makeAttachedFileBody(ContentVersion vobj) {

        String content = EncodingUtil.base64Encode(vobj.VersionData);
        String body = '--' + boundaryStr + '\r\n';
        body += 'Content-Disposition: form-data; name="attachedfiles"; filename="' + EncodingUtil.urlEncode(vobj.Title,'UTF-8') + '.' +vobj.FileType + '";\r\n';
        body += 'Content-Type: application/octet-stream\r\n\r\n';
        body += content;
        body += '\r\n';

        return body;
    }
                                                                                                                                       
    public static String getUUID() {
        Blob b = Crypto.GenerateAESKey(128);
        String h = EncodingUtil.ConvertTohex(b);
        String uuid = h.SubString(0,8)+ '-' + h.SubString(8,12) + '-' + h.SubString(12,16) + '-' + h.SubString(16,20) + '-' + h.substring(20);
        return uuid;
    }
}

4.SFDC:開発者コンソールからリクエストを実行して、ServiceNowで結果を確認する。

SFDCの開発者コンソールから匿名コマンド実行でリクエストを送信する。

Muleアプリケーションから、レスポンス200が返却されることを確認する。

ServiceNowでファイルがアップロードされていることが確認できた。
※アップロードできるファイルサイズは5MBとされているが、実際は3.5MB位なので注意が必要。

さいごに

 いかがだったでしょうか?
 SalesforceとServiceNow間のマルチパート形式でファイル連携するMuleアプリケーションの紹介しました。
 同じような要件が発生した場合は、是非試してみてください。では!