2012年12月4日火曜日

[force.com] CSV添付のメールをスケジューリングして送る

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

とある案件で毎日CSVをメールに添付して送る要件があり、実装できるまでの顛末のメモです。 問題は、添付するCSVの文字コードがShift_JIS、改行コードがCRLFじゃないといけないこと…

1. まずはControllerとVisualforceでSJISのCSVを吐き出してみる

public class CSVController {
    public String getCRLF(){return '\r\n';}
    public List<Account> accounts{get;set;}
    public CSVController() {
       accounts = [select id, name from Account];
    }
 }

{!a.id},{!a.name}{!CRLF}

こんな感じで、/apex/CSVPage でSJISのCSVがダウンロードできた。簡単すぎる。

2. メール添付して送ってみる

スケジュールApexでスケジューリングする予定なのでScheduableインタフェースを実装して、こんな感じで。

global class CSVMail implements Schedulable {
    global void execute(SchedulableContext sc) {
        Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
        msg.setSubject('テスト');
        msg.setPlainTextBody('テスト');
        msg.setToAddresses(new String[]{'pomu0325@gmail.com'});
       
        Messaging.EmailFileAttachment att = new Messaging.EmailFileAttachment();
        att.setFileName('テスト.csv');
       
        // 添付CSVをVisualforceページから読み込む
        PageReference page = new PageReference('/apex/CSVPage');
        att.setBody(page.getContent());
        msg.setFileAttachments(new Messaging.EmailFileAttachment[]{att});

        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{msg});
    }
}

開発者コンソールで、new CSVMail().execute(null); とかすればメール送信成功! あとはスケジュール設定するだけと思って「Apexをスケジュール」しても、エラーで送られないんですね…

PageReferenceのリファレンス見てみると、「This method can't be used in: … Scheduled Apex …」って使えないじゃん…どうしよう。

3. 外部のサーバで文字コードだけ変換する

Apexで文字コード変換するのが簡単にはいかなさそうなので…外部サーバで文字コード変換する為だけのapiの様なものを置いて、コールアウトで使ってみる作戦。

ScalaのUnfilteredてフレームワークで、こんな感じでデプロイしてみた。
※自分は某G社のクラウドに置きましたがherokuに置くとかいいですね!

  object UtilPlan {
    def intent = Intent {
      case r @ POST(Path("/utils/nkf")) & QueryParams(p) =>
        val encoding = p("encoding").headOption getOrElse "MS932"
        val payload = new String(Body.bytes(r), "utf-8")
        
        Ok ~> ResponseBytes(payload.getBytes(encoding))
    }
  }

これを使って、Apex Classはこんな感じに。 実行前に「セキュリティのコントロール>リモートサイトの設定」で外部サーバのURLの追加も忘れずに。

global class CSVMail implements Schedulable {
    global void execute(SchedulableContext sc) {
        Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
        msg.setSubject('テスト');
        msg.setPlainTextBody('テスト');
        msg.setToAddresses(new String[]{'pomu0325@gmail.com'});
       
        Messaging.EmailFileAttachment att = new Messaging.EmailFileAttachment();
        att.setFileName('テスト.csv');
       
        /*
        // 添付CSVをVisualforceページから読み込む
        PageReference page = new PageReference('/apex/CSVPage');
        att.setBody(page.getContent());
        */
        // 添付CSVを文字列で生成して外部サーバで文字コード変換
        String csv = CSVMail.csv([select id, name from Account]);
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://ひみつ/utils/nkf');
        req.setMethod('POST');
        req.setBody(csv);

        Http h = new Http();
        HttpResponse res = h.send(req);
        att.setBody(res.getBodyAsBlob());
       
        msg.setFileAttachments(new Messaging.EmailFileAttachment[]{att});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{msg});
    }
   
    private static String csv(List<Account> accounts) {
        String csv = '';
        for (Account a : accounts) {
            csv += a.id + ',' + a.name + '\r\n';
        }
        return csv;
    }
}

これもまた、開発者コンソール等からの実行だとOKだけど、スケジュールするとコールアウトがダメ… と思ったけど@future(callout=true)を付ければ非同期実行でコールアウト呼べることが判明したので@futureなメソッドにメール送信の処理を移動して完成!

global class CSVMail implements Schedulable {
    global void execute(SchedulableContext sc) {
        CSVMail.sendCSV();
    }
   
    @future(callout=true)
    private static void sendCSV() {

        Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
        msg.setSubject('テスト');
        msg.setPlainTextBody('テスト');
        msg.setToAddresses(new String[]{'pomu0325@gmail.com'});
       
        Messaging.EmailFileAttachment att = new Messaging.EmailFileAttachment();
        att.setFileName('テスト.csv');
       
        /*
        // 添付CSVをVisualforceページから読み込む
        PageReference page = new PageReference('/apex/CSVPage');
        att.setBody(page.getContent());
        */
        // 添付CSVを文字列で生成して外部サーバで文字コード変換
        String csv = CSVMail.csv([select id, name from Account]);
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://ひみつ/utils/nkf');
        req.setMethod('POST');
        req.setBody(csv);

        Http h = new Http();
        HttpResponse res = h.send(req);
        att.setBody(res.getBodyAsBlob());
       
        msg.setFileAttachments(new Messaging.EmailFileAttachment[]{att});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{msg});
    }
   
    private static String csv(List<Account> accounts) {
        String csv = '';
        for (Account a : accounts) {
            csv += a.id + ',' + a.name + '\r\n';
        }
        return csv;
    }
}