SvfCloudProcedure(共通のApex クラス)
すべてのサンプルが使用する、共通のApex クラスです。
/** SVF Cloudプロシージャー機能を使用するためのクラス 1回のプロシージャー実行ごとに、SvfCloudProcedureオブジェクトを生成して使用してください。 **/ global with sharing class SvfCloudProcedure { // SVF Cloud WebAPIのベースURL private final String svfCloudBaseURL; // SVF Cloudへの通信で使用する、「証明書と鍵の管理」に登録されたクライアント証明書の名前 private final String httpsClientCertificateName; // 実行するプロシージャー名 private final String procedureName; // SVF CloudからSalesforce Rest APIを実行するSalesforceユーザ名 private final String execSalesforceUsername; // SVF Cloud プロシージャー処理受付REST private final String svfCloudProcedurePath = '/procedures/'; // SVF Cloud 印刷状況の取得REST private final String svfCloudActionPath= '/actions/'; // プロシージャー呼び出しのレスポンスとして得られる、成果物URL global String locationUrl { get; set; } // プロシージャー呼び出しのレスポンスとして得られる、アクションID global String actionId { get; set; } // 印刷状況取得のレスポンスとして得られる、処理状況 global Action action { get; set; } // 成果物ダウンロード時のレスポンスとして得られる、content-type global String artifactContentType { get; set; } /** SVF Cloud プロシージャー実行でHTTPエラーが発生したことを示す共通例外クラス **/ global virtual with sharing class SvfCloudException extends Exception { SvfCloudException(String stage, String actionId){ this.stage = stage; this.actionId = actionId; } // 本サンプルコードにおける、エラー発生箇所 global String stage { get; set; } // アクションID(処理ID)。プロシージャーが処理を受け付けた時に得られます。 // SVF Cloud Managerの処理状況画面に「処理ID」として表示されます。 global String actionId { get; set; } } /** SVF Cloud プロシージャー実行でHTTPエラーが発生したことを示す例外クラス **/ global with sharing class SvfCloudHttpException extends SvfCloudException { SvfCloudHttpException(String stage, String actionId, Integer httpStatus, String HttpBody){ super(stage,actionId); this.httpStatus = httpStatus; this.httpBody = httpBody; } // エラー受信時のHTTPレスポンスステータス global Integer httpStatus { get; set; } // エラー受信時のHTTPレスポンスボディ global String httpBody { get; set; } } /** SVF Cloud プロシージャーでエラーが発生したことを示す例外クラス **/ global with sharing class SvfCloudActionException extends SvfCloudException { SvfCloudActionException(String stage, String actionId, String actionState){ super(stage,actionId); this.actionState = actionState; } // エラー発生時の印刷ステータス global String actionState { get; set; } } /** SVF Cloud WebAPIの印刷状況レスポンスJSONオブジェクトに含まれる成果物情報を表します。 **/ global with sharing class Artifact { global String path { get; set; } // 成果物ファイル名 global String name { get; set; } // 成果物名 global Integer pages { get; set; } // ページ数 } /** SVF Cloud WebAPIの印刷状況レスポンスJSONオブジェクトを表します。 **/ global with sharing class Action { global String id { get; set; } // アクションID(処理ID) global String code { get; set; } // エラーコード global String state { get; set; } // 印刷ステータス global Artifact artifact { get; set; } // 成果物情報 } /** コンストラクタ @param svfCloudTenantId SVF CloudのテナントID @param certName クライアント証明書として使用する、Salesforceの「証明書と鍵」に登録した証明書の名前 @param execSalesforceUsername SVF CloudからSalesforceへアクセスする際のSalesforceユーザ名 @param procedureName SVF Cloudに作成したプロシージャーの名前 **/ global SvfCloudProcedure(String svfCloudTenantId, String certName, String execSalesforceUsername, String procedureName){ this.svfCloudBaseURL = 'https://'+svfCloudTenantId+'.secure.svfcloud.com/api/v1'; this.httpsClientCertificateName = certName; this.procedureName = procedureName; this.execSalesforceUsername = execSalesforceUsername; } /** SVF Cloudへプロシージャーの実行を依頼する。 SVF Cloud側は、プロシージャー実行を受付しレスポンスをすぐに返す。処理自体は、その後にバックグラウンドで実行される。 @param recordIdList SVF Cloudで処理するレコードIDのリスト。 プロシージャー設定のパラメーター'id'に、シングルクォーテーションで囲まれたレコードIDがカンマ区切りで連結されて渡される。 @param paramsMap SVF Cloudのプロシージャーのパラメーターに設定された項目を上書きするMap情報。 Mapのkeyは、プロシージャー設定のパラメーター名を指定する。Mapのvalueは、シングルクォーテーションで囲まれる。 @return アクションID **/ global String executeProcedure( List<String> recordIdList, Map<String,String> paramsMap ) { String location; HttpRequest req = new HttpRequest(); Http http = new Http(); HttpResponse res = new HttpResponse(); req.setMethod('POST'); // 「証明書と鍵の管理」に設定した証明書の名前を指定します。 req.setClientCertificateName(httpsClientCertificateName); // Salesforceユーザ名を指定します。Salesforceからデータを取得する際の実行ユーザとして使用されます。 req.setHeader('X-SVFCloud-Subject', execSalesforceUsername); // タイムゾーンを指定します。SVF Cloudで帳票データ作成時の日時処理に使用されます。 req.setHeader('X-SVFCloud-TimeZone', UserInfo.getTimeZone().toString()); String endpoint = svfCloudBaseURL + svfCloudProcedurePath + procedureName; req.setEndpoint(endpoint); List<String> paramList = new List<String>{}; if ( recordIdList != null && recordIdList.size()>=0 ) { // プロシージャー設定のパラメーター"id"と"limit"を作成します。 String ids = '\'' + String.join(recordIdList,'\',\'') + '\''; paramList.add('id='+EncodingUtil.urlEncode(ids,'UTF-8')); paramList.add('limit='+String.valueOf(recordIdList.size())); } if ( paramsMap!=null && paramsMap.size()>0 ){ for ( String key: paramsMap.keySet() ){ paramList.add(key + '=' + EncodingUtil.urlEncode(paramsMap.get(key),'UTF-8')); } } String body = String.join(paramList,'&'); System.debug('form body: '+body); req.setBody(body); req.setHeader('Content-type', 'application/x-www-form-urlencoded'); req.setHeader('Content-Length',String.ValueOf(body.length())); req.setCompressed(false); try { res = http.send(req); System.debug('statusCode: ' + res.getStatusCode()); // 307リダイレクトは必ず行われます。 if (String.isEmpty(res.getStatus()) || res.getStatusCode()!=307 ){ throw new SvfCloudHttpException('CALL_SECURE_PROCEDURE', null, res.getStatusCode(), res.getBody()); } // リダイレクト先へ転送します。 endpoint = res.getHeader('Location'); System.debug('Redirect URL: '+endpoint); req.setEndpoint(endpoint); res = http.send(req); System.debug('statusCode: ' + res.getStatusCode()); // プロシージャー呼び出しの結果、正常な場合は、ダイレクトプリントでは202、その他は303が返ってきます。 if (!String.isEmpty(res.getStatus()) && res.getStatusCode()!=303 && res.getStatusCode()!=202 ){ System.debug(res.getBody()); throw new SvfCloudHttpException('CALL_EXECUTE', null, res.getStatusCode(), res.getBody()); } // Location ヘッダーには、プロシージャー呼び出しの結果生成されるPDFファイル等の成果物を取得するためのURLが設定されます。 // また、URLには印刷状況の取得に必要なアクションIDが含まれます。 location = res.getHeader('Location'); }catch(System.CalloutException e) { System.debug('Callout error: '+ e); throw e; } this.locationUrl = location; this.actionId = parseActionId(location); return this.actionId; } /** #executeProcedure()で実行したプロシージャー呼び出しの印刷ステータスを取得します。 正常終了するかエラーになるまで、パラメーターに指定したrestTimeout秒待機します。 コールアウトの制限事項やApexガバナ制限に抵触しないように、restTimeoutの値を指定してください。 印刷ステータスでプロシージャー処理がエラーになったことが確認できた場合、例外を発生させています。 なお、1回のプロシージャー呼び出しに対して本メソッドを複数回呼び出した場合、 2回目以降はrestTimeoutの値にかかわらず待機時間が0秒となることがありますので、 本メソッドをループ呼び出しすることはしないでください。 @param restTimeout 最大待機時間(秒) @return 印刷ステータスが 印刷データ作成終了以降かどうか **/ global void waitStatus(Integer restTimeout) { action = retrieveActionStatus(this.actionId, restTimeout); System.debug('current state: '+action.state); if ( action.state == '3' // [印刷ステータス] 異常終了 || action.state == '5' // [印刷ステータス] キャンセル || action.state == '6' // [印刷ステータス] 強制終了 ) { // 正常に終了しなかった状態 throw new SvfCloudActionException('ACTION_ERROR', this.actionId, action.state ); } return; } /** 指定されたアクションの印刷状況をSVF Cloudから取得します。 @param actionId アクションID(処理ID) @param restTimeout タイムアウト値(秒) **/ global Action retrieveActionStatus(String actionId, Integer restTimeout){ Action action; HttpRequest req = new HttpRequest(); Http http = new Http(); HttpResponse res = new HttpResponse(); req.setMethod('GET'); req.setClientCertificateName(httpsClientCertificateName); // Salesforceユーザ名を指定します。 req.setHeader('X-SVFCloud-Subject', execSalesforceUsername); // タイムゾーンを指定します。 req.setHeader('X-SVFCloud-TimeZone', UserInfo.getTimeZone().toString()); String endpoint = svfCloudBaseURL+svfCloudActionPath+actionId+'?timeout='+String.valueOf(restTimeout); System.debug('Action URL: '+endpoint); req.setEndpoint(endpoint); req.setHeader('Accept','application/json'); req.setCompressed(false); try { res = http.send(req); System.debug('statusCode: ' + res.getStatusCode()); // 307リダイレクトは必ず行われます。 if (String.isEmpty(res.getStatus()) || res.getStatusCode()!=307 ){ throw new SvfCloudHttpException('CALL_SECURE_ACTION', null, res.getStatusCode(), res.getBody()); } endpoint = res.getHeader('Location'); System.debug('Action Redirect URL: '+endpoint); req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(endpoint); req.setHeader('Accept','application/json'); req.setTimeout(120000); Long startTime = DateTime.now().getTime(); res = http.send(req); Long finishTime = DateTime.now().getTime(); System.debug('elapsed msec: '+(finishTime-startTime)+' statusCode: ' + res.getStatusCode()); if(String.isEmpty(res.getStatus()) || res.getStatusCode()!=200){ throw new SvfCloudHttpException('ACTION', actionId, res.getStatusCode(), res.getBody()); } String body = res.getBody(); // レスポンスから処理状況を取得します。 action = null; JSONParser parser = JSON.createParser(body); if ( parser.nextToken() != null ) { action = (Action)parser.readValueAs(Action.class); } if ( action == null || action.state == null ){ // レスポンスに印刷ステータスが含まれていない場合 System.debug(body); throw new SvfCloudActionException('INVALID_ACTION_RESULT', actionId, null); } System.debug('artifact: '+action.artifact.path); }catch(System.CalloutException e) { System.debug('Callout error: '+ e); throw e; } return action; } private String parseActionId(String locationUrl) { // LocationヘッダのURLからアクションIDを取得します。 // ダイレクトプリント以外の場合、URLのquery parameterにAction IDが入っています。 Pattern pt = Pattern.compile('action=(.+)&ticket'); Matcher matcher = pt.matcher(locationUrl); String id = null; if ( matcher.find() ) { id = matcher.group(1); return id; } // ダイレクトプリントの場合は、URLのパスにAction IDが入っています。 pt = Pattern.compile('actions/([0-9a-f-]*)$'); matcher = pt.matcher(locationUrl); if ( matcher.find() ) { id = matcher.group(1); return id; } System.debug('invalid location URL: '+locationUrl); throw new SvfCloudActionException('GET_ACTION_ID',null,null); } /** #executeProcedure()で実行したアクションが生成したファイルをSVF Cloudからダウンロードする。 **/ global Blob downloadArtifact() { String downloadUrl = this.locationUrl; HttpRequest req = new HttpRequest(); Http http = new Http(); HttpResponse res = new HttpResponse(); // 成果物のダウンロードを行います。 req.setMethod('GET'); req.setEndpoint(downloadUrl); req.setCompressed(false); req.setHeader('Accept','application/octet-stream'); try { System.debug('download URL: ' + downloadUrl); res = http.send(req); System.debug('statusCode(1): ' + res.getStatusCode()); if ( !String.isEmpty(res.getStatus()) && res.getStatusCode()==303 ){ // 303 See Other が返ってきたら、リダイレクトします。 String endpoint = res.getHeader('Location'); System.debug('redirect URL: '+endpoint); req = new HttpRequest(); req.setMethod('GET'); req.setEndpoint(endpoint); req.setHeader('Accept','application/octet-stream'); res = http.send(req); System.debug('statusCode(2): ' + res.getStatusCode()); } if (!String.isEmpty(res.getStatus()) && res.getStatusCode()==200 ){ artifactContentType = res.getHeader('Content-Type'); System.debug('download content type = '+artifactContentType); return res.getBodyAsBlob(); } // ERROR throw new SvfCloudHttpException('DOWNLOAD_ERROR', this.actionId, res.getStatusCode(), res.getBody()); }catch(System.CalloutException e) { System.debug('Callout error: '+ e); throw e; } } }