2013年5月26日日曜日

SalesforceからiPhoneへPush notificationを送る

とんかつ専門店のご飯が妙に美味いのはなぜ?
上の写真はまったく関係ありません。

週末、何かネタを探していたら、www.parse.comというサービスに関する記事が見つかりました。最近ではPush Notification代行サーバもいろいろあるのですが、これは余計な機能がないぶんシンプルで安い。

そんなわけで、chatter上に「$go message」と書くと、triggerが作動してForce.comのhttp calloutからparse.comのAPIを叩き手元のiPhoneにメッセージが届く、というのを作ってみました。

■まず、Push Notificationを試す■

試すと言いますか、こちらの記事を参考に、いや、そのまま実行してみます。

Remote Push Notification ASPサービスを試す

Parse、一発で動いてPushされました。いやー、いままでこんなに簡単にPushできたのって初めてかもしれない。10分か15分くらいで動いてしまいました。



■FeedItemのTriggerからParseのREST APIを呼ぶ■

さて、ここからはForce.com上での作業。TriggerからREST APIを呼ぶことはできないので、Triggerからバッチを起動、バッチからcall outする作戦。

忘れないうちに、まずはお約束のリモートサイトの設定。
  管理者設定>セキュリティのコントロール>リモートサイトの設定
これでhttps://api.parse.com/を登録しておきます。

■Batchを作る■

Developer ConsoleからClass CallParseを定義、IterableなBatchApexでParse.comのAPIを叩きます(コードは末尾)。

■Triggerを作る■

例によってDeveloper ConsoleからFeedItem上のキーワード $go を検出してBatch Apexを呼び出すコードを書きます(コードは末尾)。

■動かない■

一応、Salesforce上で
  管理者設定>監視>デバッグログ
を設定してから、Chatter上にメッセージを書いてみる。

…動かない。デバッグログを見ると、

05:50:02.039 (39869000)|CALLOUT_REQUEST|[29]|System.HttpRequest[Endpoint=https://api.parse.com/1/push, Method=POST]
05:50:02.175 (175302000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:661
05:50:02.175 (175393000)|CALLOUT_RESPONSE|[29]|System.HttpResponse[Status=Bad Request, StatusCode=400]
05:50:02.175 (175420000)|HEAP_ALLOCATE|[29]|Bytes:130
05:50:02.175 (175438000)|SYSTEM_METHOD_EXIT|[29]|System.Http.send(ANY)

と出ていました。ちくしょう、どうせ一発で動くと思ってねぇですよ←涙目 なので、Apex側にコードを入れて
    System.debug(res.getBody());
    System.debug(res.getStatus());
    System.debug(res.getStatusCode());
検証してみたところ
    16:43:08.153 (153194000)|USER_DEBUG|[36]|DEBUG|{"code":107,"error":"This endpoint only supports Content-Type: application/json requests, not : application/json."}
という結果が。

ソースを改めて見なおしたら、

    req.setHeader('Content-Type : ', 'application/json');

なんてバカなコーディングを発見。直したら無事動作。




以下コードを張っておきます。iPhone側はparse.comのサンプルを貼っただけです。

■Trigger■


trigger KickFromChatter on FeedItem (before insert) {

    FeedItem[] feeds = Trigger.new;
    List<FeedItem> arrayFeedItem = new List<FeedItem>();
    
    for (FeedItem feed : feeds) {
        String strBody = feed.Body;
        if (strBody != null && strBody.contains('$go')) {
            arrayFeedItem.add(feed);
        }
    }
    
    if (arrayFeedItem.size() > 0) {
        CallParse batch = new CallParse();
        batch.arrayFeedItem = arrayFeedItem;
        
        Database.executeBatch(batch);
    }
}


■Batch Apex■


global class CallParse implements Database.batchable<FeedItem>, Database.AllowsCallouts {
    
    global List<FeedItem> ArrayFeedItem;
    
    global Iterable<FeedItem> start(Database.BatchableContext info) {
        Iterable<FeedItem> i = arrayFeedItem;
        
        return i;
    }
    
    global void execute(Database.BatchableContext info, List<FeedItem> feeds) {
        for (FeedItem feed : feeds) {
            String str = feed.Body;
            String strPayload = '{"channels" : [""], "data" : {"alert" : "' + str + '"}}';
            Blob blobPayload = Blob.valueOf(strPayload);
            
            // Instantiate a new http object
            Http h = new Http();
            
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://api.parse.com/1/push');
            req.setMethod('POST');
            
            req.setHeader('X-Parse-Application-Id', 
                                   'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
            req.setHeader('X-Parse-REST-API-Key',
                                   'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
            req.setHeader('Content-Type',
                                   'application/json');
            
            req.setBody(strPayload);
            
            // Send the request, and return a response
            HttpResponse res = h.send(req);
            
            System.debug(res.getBody());
            System.debug(res.getStatus());
            System.debug(res.getStatusCode());
        }
    }
    
    global void finish(Database.BatchableContext info) {
    }
    
}


2013年5月8日水曜日

Force.com:添付ファイルの全文検索

ChatterにMS-Officeのファイルをアップロードし、ブラウザでログインして「検索」から探すとファイルの中身をちゃんと探してくれるのですが、APIからFINDを使ってもヒットせず苦労していましたが、解決策が見つかったのでメモ。

■ContentVersionにあった■

Chatterにアップロードしたファイルを全文検索で探し出したい。

とりあえず、ファイルがどこに格納されているかをコンソールでSOQLで探したところ、ContentVersionにそれらしいレコードが見つかりました。その結果を踏まえてEnterprise/WSCで以下のSOSLを実行したところ、うまく検索してくれました。

  FIND {語句} RETURNING ContentVersion (Id, FirstPublishLocationId, IsLatest, PathOnClient)

ここで、IsLatest = trueが最新版を示すフラグ、PathOnClientはファイル名です。FirstPublishLocationIdには関連するオブジェクトのIDが入っているので、たとえばChatterからアップロードした場合には、
  SELECT Id, Body FROM FeedItem WHERE Id = cvFirstPublishLocationId
てなことを書けば探し出せます。

■試行錯誤■

何もパラメータなしに
  FIND {語句} 
とすると一応全ファイルが対象になるはず。そうでなくとも
  FIND {語句} RETURNING FeedItem (Id, Body, CreatedDate)
などと書けばヒットする、はず。

でも、どっちもダメでした。

それとPDFもダメでした。日本語のPDFはほぼ全滅、英語版PDFもヒットしないことの方が多いという状況です。いろいろな文書ファイルで試してますが、Office 2007ファイルについては今のところ漏れなくヒットします。

マカーの私としては、PDFが対象外ってのはちょっと困るのですが…。

以上、ご参考になれば幸いです。

2013年5月6日月曜日

「倉橋浩一」

でググると、今のところは私の記事がトップに来るけど、日本大学に同姓同名の人がいるな。Facebookでは同じ読みの人が沢山出てくる。

そろそろ何かアウトプットしないといけないなぁ…。

■太っていてびっくり■
OOエンジニアの輪! ~ 第 23 回 倉橋 浩一 さんの巻 ~

■バカというのは治らない病気■
倉橋浩一@WebObjectsは、YS-11を心より愛す|【Tech総研】

■懐かしい…。■
じつはWebObjectsで飯食ってます(連載一覧)

■私の前が林信行さんで次が竹林賢さんってスゲエ■
林檎いとしや Vol.66 倉橋浩一 人生への投資効果