dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

follow us in feedly

[Salesforce]スケジュール済みApexやトリガでVisualforceをpdfファイルに変換する方法(前編)

以前の記事で、Visualforce ページを pdf ファイルとして添付してメール送信する方法について書いた。

この時、問題点として PageReference クラスの getContent() メソッド
スケジュール済み Apex(Apex スケジューラ?)やトリガなどの非同期処理内では実行できないというのがあった。

この問題を解決するためなんとかしてみたけどなんとかならなかった話。

やりたいこと
  • スケジュール済み Apex を使い、毎週(月)決まった時間に Apex による処理を実行
  • Apex 処理内で特定の Visualforce ページを pdf ファイルとして出力
  • 添付ファイルとしてメールに添付して特定のアドレスに送信

TL;DR

  • Visualforce(PageReference クラス)の getContent を使って取得する方法は不可
    • トリガやスケジュール済み Apex から実行するとうまくいかないため
  • メール送信処理を Apex REST web サービスにするというややハックっぽい方法でもだめ
    • getContent が 9 月ごろにコールアウト扱いになるため REST web サービスから呼べなくなる
  • TODO:「Visualforce テンプレート」というメールテンプレートを使うとできる、かも?
    • <messaging:attachment> タグで添付ファイルが定義できるらしいので

(2015/07/10追記)
つづき書きました。

使用する Visualforce ページ

今回用意した Visualforce ページ。
商談の ID を受け取り、商談名と商談商品の一覧を表示するという単純なもの。

コード

スケジュール設定して実行してみる

では、さっそく Visualforce ページを pdf としてメールに添付、という処理を Apex スケジューラから実行してみる。

コード

メール添付の部分については 以前の記事 で紹介したので結果だけ。
また、Apex をスケジュール実行するには Schedulable インターフェースを実装したクラスを使います。

スケジューラを登録する

Apex スケジューラの動作を試すには、開発者コンソールで次のようなコードを実行すると良いです。

ScheduledApex scheduled = new ScheduledApex();
// クーロンの設定例:毎時25分に実行する
System.schedule('PDF attachment mail job', '0 25 * * * ?', scheduled);

これは別に

開発 > Apex クラス > Apex をスケジュール

から GUI で設定してもいいんだけど、00分ちょうどしか設定できないので今すぐ実行してみたい時には使い勝手が悪い。

スケジュールされたかどうかは

ジョブ > スケジュール済みジョブ

から確認できます。

結果

受信した pdf を開くと、Mac だと次のようなエラーが。

f:id:dackdive:20150628083101p:plain

やはり getContent() を非同期処理から呼び出すのは無理なようです。

解決策

色々調べてみて、解決策として可能性のありそうな方法を2つほど見つけた。

  1. メール送信部分を REST web サービスとして定義する ← (追記)うまくいかなかった
  2. Visualforce テンプレートを使い、添付ファイルを messaging:attachment で定義する

方法1: メール送信部分を REST web サービスとして定義する

参考にしたのはこの記事。
Send Email with Generated PDF as attachment from Trigger | Salesforce and Web

どうやら、Visualforce を getContent して添付ファイル化、メール送信という処理を REST web サービスにしてしまい、
Http コールアウトで呼び出すという(かなり強引な)やり方のよう。

で、書いてみた。

問題点

一見うまくいきそうだけど、 スケジュール実行する Apex から呼び出すと UserInfo.getSessionId() でセッション ID が取れず null になる。
(なので、トリガから実行だとおそらくうまくいく。試してないけど)

というわけで、認証周りでつまずきました。
OAuth2 とかの認証方式でやればうまくいくんだろうけど、ここで一旦お手上げ。


(追記)

Twitter でこんなコメントをいただきました。

そういえば今回の Summer'15 のリリースノートに...!と思って確認したらあった。
http://releasenotes.docs.salesforce.com/ja-jp/summer15/release-notes/rn_vf_getcontent_callout_cruc.htm

PageReference は、Visualforce ページのインスタンス化への参照です。getContent() および getContentAsPDF() インスタンスメソッドでは、表示されるページのコンテンツがそれぞれ HTML および PDF として返されます。この更新では、これらのメソッドに対して行われたコールがコールアウトとして動作し、コールトランザクションの制限に関してコールが追跡されます。

さらに

REST web サービスからコールアウトは認められていない ようです。

これに関しては公式ドキュメントには記述が見つからなかったけど、おそらくここで議論されてる話。
rest api - Callout loop not allowed error - Salesforce Stack Exchange

ためしに「重要な更新」からこの機能を有効化し、

上記のコードをトリガから実行するように(そしてセッション ID を適切に渡すように)したところ
以下のエラーが発生することを確認した。

System.CalloutException: Callout loop not allowed

というわけで、スケジュール済み Apex では認証周りを解決しないとうまくいかないだけでなく、
解決したとしても将来的には動作しなくなることが判明。
無事に詰みました。

(追記ここまで)


方法2: Visualforce テンプレートを使い、添付ファイルを messaging:attachment で定義する

Visualforce テンプレートを使ってメール送信すれば添付ファイルも簡単に定義できる、という話が。

deferloader » [salesforce]Visualforeメールテンプレートを使ってメール送信

基本的にはこのような書き方になるらしい。

<messaging:emailTemplate subject="test" recipientType="Contact" relatedToType="*****">
    <messaging:plainTextEmailBody >
        (メール本文を書く)
    </messaging:plainTextEmailBody>
    <messaging:attachment renderAs="PDF" filename="添付書類.pdf">
        (添付ファイルのコードを書く)
    </messaging:attachment>
</messaging:emailTemplate>

なるらしいんだけど、以下のような点が色々解決(というか確認)できなくて時間切れ。
ただ、こっちについては引き続き調べてみればいけるかも。。。という印象。

  • 商談 ID みたいなパラメータを渡すにはどうするの?
    • relatedToType とかいうパラメータあるけど
  • 別で定義した Visualforce を埋め込む形で定義することはできる?
    • Visualforce コンポーネントはいける、という情報は得たけど試してない
    • その場合もパラメータどっから渡す?
    • apex:include は使ったらエラーになった。深く調査してない

おわりに

というわけで、かなり中途半端になったけど時間を見つけて再チャレンジする予定。

リファレンス