tag:blogger.com,1999:blog-78544458462724881122024-03-13T16:10:58.282+09:00pomu0325ScalaとかObjective-Cとかforce.comとかで開発してます。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.comBlogger38125tag:blogger.com,1999:blog-7854445846272488112.post-3129544906293543102014-02-07T22:46:00.002+09:002014-02-07T22:47:08.210+09:00[force.com] 変更セットとかパッケージが到着するのを監視して通知する方法<span style="font-family: Arial; font-size: 13px;">変更セットやパッケージがなかなかデプロイしたいインスタンスに到着しなくて<strike>イライラ</strike>やきもきしたことはないでしょうか?</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">何度も⌘R(またはF5)を押して時間を無駄にしないでいい方法を見つけました。</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">Auto Refresh Plus(以後ARP)というChrome拡張を使うと、指定した秒数で自動的にページをリロードしてくれて、指定したキーワードがページ内に現れたら通知をしてくれます!!!</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">まずは、Chrome Web Storeから、ARPをインストールします。</span><br />
<span style="color: #042eee; font-family: Arial; font-size: 13px;"><u>https://chrome.google.com/webstore/detail/auto-refresh-plus/oilipfekkmncanaajkapbpancpelijih</u></span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">設定画面で、キーワードをウォッチする機能、Page Monitorを有効にします。</span><br />
<span style="font-family: Arial; font-size: 13px;">気づきやすい様に通知時に音も鳴る様にしておきます。</span><br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUlSoZrrKiwvqm3d-EV65l0UCPYLMOgwxM2VMuNiqqTHXTeNCkYBLmCK5SJEt8iXbKyscqwRGafvpQ1aFlgIvJHVIwWIV06gibtmivSWRtEBApeoT1O15S82XVGSf7Ru1-BM695vvUo9k/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2014-02-07+22.09.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUlSoZrrKiwvqm3d-EV65l0UCPYLMOgwxM2VMuNiqqTHXTeNCkYBLmCK5SJEt8iXbKyscqwRGafvpQ1aFlgIvJHVIwWIV06gibtmivSWRtEBApeoT1O15S82XVGSf7Ru1-BM695vvUo9k/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2014-02-07+22.09.03.png" height="283" width="320" /></a></div>
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">準備はこれで完了です。</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<h2>
<span style="font-family: Arial; font-size: 13px;">使い方</span></h2>
<div>
<span style="font-family: Arial; font-size: 13px;"><br /></span></div>
<span style="font-family: Arial; font-size: 13px;">sandboxから、本番環境に変更セットを送ります。</span><br />
<br />
<span style="font-family: Arial; font-size: 13px;"></span>
<span style="font-family: Arial; font-size: 13px;">本番環境で、「受信変更セット」のページを開きます。まだ来てないですね。ここでARPの出番です。</span><br />
<br />
<span style="font-family: Arial; font-size: 13px;"></span>
<span style="font-family: Arial; font-size: 13px;">あまり負荷をかけない様に1分間隔くらいにして、Page Monitorのキーワードには変更セット名を入れ、スタートします。</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjolMcWSnDIoaEaCTi3-S36wyVXgpRqepz8cZjgcPutfG4hGgq4WjroizNhUAkff5rtz8ZKKXmf0e-TM7mCt9s7X9zz7C3fFuuOU2BT3HYorFY811zmRAn_lnjxa2L4uW5rkkMKj6lzEjo/s1600/2014-02-07+22.41.35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjolMcWSnDIoaEaCTi3-S36wyVXgpRqepz8cZjgcPutfG4hGgq4WjroizNhUAkff5rtz8ZKKXmf0e-TM7mCt9s7X9zz7C3fFuuOU2BT3HYorFY811zmRAn_lnjxa2L4uW5rkkMKj6lzEjo/s1600/2014-02-07+22.41.35.png" height="131" width="320" /></a></div>
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">もうこれで監視がスタートしたので、他の作業とか進めちゃいます。</span><br />
<span style="font-family: Arial; font-size: 13px;"><br /></span>
<span style="font-family: Arial; font-size: 13px;">(待ってる間に、このblogを書いています…)</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">(書き終わってしまいました…)</span><br />
<br />
<span style="font-family: Arial; font-size: 13px;">あ、通知が来ました。</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhklEaMKRCC8eZ7IqWDduPRILYBtvotQP-33UiNLOltord_vljjvSyCggbs5eGqxvKn4lK8NGr9A6DHyW2G4SdrrbUwx1N0xaDB__0VwwRNs-MOa91w2Y4EzQbhLM6xfMSUmZm__sXGn9E/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2014-02-07+22.20.53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhklEaMKRCC8eZ7IqWDduPRILYBtvotQP-33UiNLOltord_vljjvSyCggbs5eGqxvKn4lK8NGr9A6DHyW2G4SdrrbUwx1N0xaDB__0VwwRNs-MOa91w2Y4EzQbhLM6xfMSUmZm__sXGn9E/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2014-02-07+22.20.53.png" height="176" width="320" /></a></div>
<span style="font-family: Arial; font-size: 13px;">これで時間を無駄にしなくてすみますね!</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;">※同じやり方でキーワードを変えれば、パッケージがなかなか来ないとかでも監視ができます</span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;"></span><br />
<span style="font-family: Arial; font-size: 13px;"></span>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-87786201228469443942013-12-24T13:20:00.002+09:002013-12-24T13:32:25.567+09:00リードの「取引の開始」をSalesforce1アプリにパブリッシャーアクションで追加するSalesforce1に変わっても相変わらずリードの取引先変換が無い!!!と<a href="https://success.salesforce.com/ideaView?id=08730000000ky8yAAA" target="_blank">IdeaExchange</a>でも取り上げられています。<br />
<br />
ちょっと前に「<a href="https://developer.salesforce.com/forums?id=906F00000008mz0IAA" target="_blank">カレンダーをSalesforce1に追加する方法</a>」を見たのでインスパイアされてパブリッシャーアクションを簡単に作ってみました。<br />
<br />
<h3>
Visualforceページを作る</h3>
<div>
こんな感じで、先ほどのカレンダーのやつまるパクリです。<br />
{!id}でリードのIDを埋め込んで、isdtp=nvでヘッダなどを隠してます。</div>
<script src="https://gist.github.com/pomu0325/8108640.js"></script><br/>
<h3>
リードにアクションを追加</h3>
<div>
上のVisualforceページを指定します。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg03ESyZ3IQKGeZYSp-M-Te3bk_pSVNWl-6LNPFQETlW_O7tBzkBbf2BjfejtmUvquuZRcrsi9K7tgjkJIIKOxf5HCnM4Mk6XnXTIZ5KjxFG95IJ6EfOymogZqf401yE0PvhT-5YitCGpo/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2013-12-24+13.04.21.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg03ESyZ3IQKGeZYSp-M-Te3bk_pSVNWl-6LNPFQETlW_O7tBzkBbf2BjfejtmUvquuZRcrsi9K7tgjkJIIKOxf5HCnM4Mk6XnXTIZ5KjxFG95IJ6EfOymogZqf401yE0PvhT-5YitCGpo/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2013-12-24+13.04.21.png" width="315" /></a></div>
<h3>
レイアウトに追加</h3>
<div>
リードのレイアウトに作成したアクションをドラッグ&ドロップして追加。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjABZtaYw-QrfDZd2xWV9dzi9XChmnLDOcPZ8j8I8ZIDjzGP5lfHqHfkiwb-FfdQ3ijOMgkKRpjH8yhOBXGksdLL5WAWU3M0pvYEHWCr0d4xCfjojfqs69cAHkkESap8Ic9BW2FhBWyl0M/s1600/2013-12-24+13.10.47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjABZtaYw-QrfDZd2xWV9dzi9XChmnLDOcPZ8j8I8ZIDjzGP5lfHqHfkiwb-FfdQ3ijOMgkKRpjH8yhOBXGksdLL5WAWU3M0pvYEHWCr0d4xCfjojfqs69cAHkkESap8Ic9BW2FhBWyl0M/s320/2013-12-24+13.10.47.png" width="320" /></a></div>
<div>
<br /></div>
<h3>
iPhoneのSalesforce1アプリから使ってみる</h3>
<div>
標準の画面を無理矢理埋め込んだだけなので画面サイズがアレですが…とりあえず使える様になりました!</div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxwGjkCGGD2y_QFCAnFD3e8WVNfA2WegC5JJ_QlBPugCAZxHh54abtzXp4dUIdjZvgnaEXkwVeEEPTm8Qp8mQ' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
※ネタが無かったので<a href="http://atnd.org/events/45110" target="_blank">Force.com Advent Calendar 2013</a>不参加予定でしたが急遽プチネタができたので遡っての参加です</div>
<div>
<br /></div>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-34055319513506783142013-09-08T01:39:00.001+09:002013-09-08T01:44:16.181+09:00Salesforce Developer Conference Tokyo 2013 (Mini Hackレポート)<div class="p1">
Salesforce.comの開発者向けイベント、<a href="http://jp.force.com/devcon/2013/" target="_blank">Salesforce Developer Conference Tokyo 2013</a>行ってきました。</div>
<div class="p2">
<br /></div>
<div class="p1">
去年のCloudforceでもあったMini Hack、今回は豪華賞品を揃えて全4問で再登場したの挑戦しました。なんと4問提出で、抽選で1名なんと<a href="http://www.salesforce.com/jp/dreamforce/DF13/" target="_blank">Dreamforce</a>ツアーご招待。</div>
<div class="p2">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMYF6naRgJ6kFz-Z2NM-vYg54Xec-q3a0xrRsUjwIafgHbPAEQWYK0V54PpuhkvrXa-RlqAe3hESkLNwJ5d9ghnMAg8_q2PFmc6Miyp2LT3_HF1-hUNVLQIQMoA-nONBANnfFl1HKoI3I/s1600/%E5%86%99%E7%9C%9F+2013-09-06+16+07+45.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMYF6naRgJ6kFz-Z2NM-vYg54Xec-q3a0xrRsUjwIafgHbPAEQWYK0V54PpuhkvrXa-RlqAe3hESkLNwJ5d9ghnMAg8_q2PFmc6Miyp2LT3_HF1-hUNVLQIQMoA-nONBANnfFl1HKoI3I/s320/%E5%86%99%E7%9C%9F+2013-09-06+16+07+45.jpg" width="240" /></a></div>
<br />
4問の課題は以下の内容:</div>
<div class="p1">
<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917415442517538?banner=pwa&pid=5920917415442517538&oid=113885912957127906241" target="_blank">課題H「Mobileアプリケーションを作ってみよう!」</a></div>
<div class="p1">
<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917449827369746?banner=pwa&pid=5920917449827369746&oid=113885912957127906241" target="_blank">課題A「Treasure Dataアドオンを使ってみよう!」</a></div>
<div class="p1">
<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917465650399074?banner=pwa&pid=5920917465650399074&oid=113885912957127906241" target="_blank">課題C「オフラインHack - SmartStore」</a></div>
<div class="p1">
<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917472697372386?banner=pwa&pid=5920917472697372386&oid=113885912957127906241" target="_blank">課題K「Heroku Platform APIをつかってHerokuを操作しよう!」</a></div>
<div class="p2">
<br /></div>
<div class="p1">
とりあえず簡単そうなものから…と思って課題流し読みして、Aからやろうと思ったらTreaure Dataのアドオン追加がなぜかエラー…。Kからやることに。<br />
<br /></div>
<div class="p1">
<h2>
課題K「Heroku Platform APIをつかってHerokuを操作しよう!」</h2>
</div>
<div class="p1">
要は、最近公開されたPlatform APIを試してみてね、という<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917472697372386?banner=pwa&pid=5920917472697372386&oid=113885912957127906241" target="_blank">課題</a>。<a href="https://devcenter.heroku.com/articles/platform-api-quickstart" target="_blank">Platform APIのチュートリアル</a>通りにコマンドラインからcurlでAPIの動作を確認すればOK。</div>
<div class="p1">
一部、チュートリアルのコマンド例そのままだとContent-Type: application/jsonヘッダが足りなくてエラー出てたので報告しときました。</div>
<div class="p1">
Heroku使った事あれば簡単すぎますね。</div>
<div class="p2">
<br /></div>
<div class="p1">
まだTreasure Dataのアドオンが追加できなかったので次はH。</div>
<div class="p2">
<br /></div>
<div class="p1">
<h2>
課題H「Mobileアプリケーションを作ってみよう!」</h2>
</div>
<div class="p1">
こちらも数ヶ月前にリリースされた<a href="http://www2.developerforce.com/mobile/services/mobile-packs" target="_blank">Mobile Pack</a>を試してみてね、という<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917415442517538?banner=pwa&pid=5920917415442517538&oid=113885912957127906241" target="_blank">課題</a>。Mobile Packを使用して取引先の一覧を表示するモバイル用ページを作ればOK。</div>
<div class="p1">
jQuery mobile, AngularJS, Backbone.js, Knockout, appery.io, Sencha, Xamarin, Codiqaと多数のフレームワークに対応してる様です(半分くらい知らない)。</div>
<div class="p1">
とりあえず普段使っているAngularJSでやろうとサンプルコードを落としてきて動かしてみるが、app.jsとかを静的リソースに突っ込むのが面倒そうで、jQuery mobileに変更(<a href="https://twitter.com/a_kuratani" target="_blank">@a_kuratani</a>さんがjQuery mobileでやったら簡単だったと言ってたのでw)。</div>
<div class="p1">
サンプルコードの取引先責任者の一覧表示するVisualforce Pageを覗いてみるとJSに直にSOQLが…。オブジェクト名と項目名だけ変えておしまいですね。</div>
<div class="p2">
<br /></div>
<div class="p1">
<a href="https://twitter.com/mino0123/status/375865756413472769" target="_blank">@mino0123さんのツイート</a>を見て、アドオン追加が復活してるっぽかったのでAを再開。<br />
<br /></div>
<div class="p1">
<h2>
課題A「Treasure Dataアドオンを使ってみよう!」</h2>
</div>
<div class="p1">
Treasure DataのHerokuアドオンを試してみてね、という<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917449827369746?banner=pwa&pid=5920917449827369746&oid=113885912957127906241" target="_blank">課題</a>。</div>
<div class="p1">
とりあえず課題シートの指示通りにデモアプリをgithubからcloneして、コマンドラインからアドオン追加、プラグイン追加、デプロイをしてみます。</div>
<div class="p1">
デモアプリをブラウザで開いても、Hello World!が出るだけで動きが分からないのでソースを追ってみると、アクセスの都度裏でTDにログを送る様です。</div>
<div class="p1">
「サンプルアプリを修正して画面の表示を変更してください」って画面の修正だけで本当にいいのか気になりながら、変更するところはタイトルくらいしか無かったのでHello World! をHoge Hoge! みたいに書き換えて聞いてみたらこれでOKw(TD関係ないじゃん)</div>
<div class="p1">
ちなみにRubyを全然書いたことがないので初めて.erbファイルをさわった(デモアプリはsinatra)。</div>
<div class="p2">
<br /></div>
<div class="p1">
3問でMBA狙いにしようかと思ったけど時間あったから4問目も挑戦。</div>
<div class="p2">
<br /></div>
<div class="p1">
<h2>
課題C「オフラインHack - SmartStore」</h2>
</div>
<div class="p1">
Mobile SDKを使ってオフライン対応のアプリを作れ、という<a href="https://plus.google.com/photos/113885912957127906241/albums/5920917406120199281/5920917465650399074?banner=pwa&pid=5920917465650399074&oid=113885912957127906241" target="_blank">課題</a>と勝手に解釈。</div>
<div class="p1">
SmartSyncがBackbone.js使われてるとか基調講演で聞いたので、ちょっと難易度高いかなーと思って思い込んで避けてたやつ。</div>
<div class="p1">
Mobile SDKは前に落として試してたけど、念のため最新版をgit pull。</div>
<div class="p1">
iOS版のサンプルアプリをXcodeからとりあえず動かしてみて、問題を良く読んでみると…「提出方法:サンプルの動作が画面で確認できたら、お声掛けください」</div>
<div class="p1">
始める前に、終わってた。</div>
<div class="p1">
※<a href="https://twitter.com/yonet77" target="_blank">@yonet77</a>さんがサンプルコードでビルドエラーが…て困ってたのでちょっと見てみたら、最近iOS7対応したときに見覚えのある定数が。github上のmasterがすでにiOS7対応されてて、β版Xcodeじゃないとヘッダ足りなくて動かないので、β版Xcode5持ってない場合は古いリビジョン取り直さないとダメという罠</div>
<div class="p2">
<br /></div>
<div class="p1">
4問解いておいて、Dreamforceにするか、MBAにするかギリギリまで迷ったけど、<a href="https://twitter.com/yonet77" target="_blank">@yonet77</a>さんがDreamforceに1人目で申し込んだので、このままだと確定で面白くないなーということで同じくDreamforceに申し込んだら、なんと抽選当たってしまいました!<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXtM4JYP0ETk_BUQuavziHN0_2MYD-AMkSKiTY7zQ9Z0LMhyphenhyphenoBrAlnubTeXVJ7czWMd5o592aScgcli4YbqxC4GEwnwvOlCMqIOcgZcI4kQaKlcRu59ckNUm4ouzkjY45KbmsY4xg3oKI/s1600/%25E5%2586%2599%25E7%259C%259F+2013-09-06+18+27+49.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXtM4JYP0ETk_BUQuavziHN0_2MYD-AMkSKiTY7zQ9Z0LMhyphenhyphenoBrAlnubTeXVJ7czWMd5o592aScgcli4YbqxC4GEwnwvOlCMqIOcgZcI4kQaKlcRu59ckNUm4ouzkjY45KbmsY4xg3oKI/s320/%25E5%2586%2599%25E7%259C%259F+2013-09-06+18+27+49.jpg" width="240" /></a></div>
<br /></div>
<div class="p1">
Developer Conference参加自体も直前で決めたのに、なんか申し訳ない…<br />
<br /></div>
<div class="p1">
<h3>
最後に、傾向と対策</h3>
</div>
<div class="p1">
<div class="p1">
<ul>
<li>なんか難しそうだけど課題の意図はとりあえず手を動かしてさわってみてね、という所だと思うのでチュートリアルレベル。とにかくやってみる</li>
<li>git, herokuは使えて当たり前? 環境は整えておく</li>
<li>ノートパソコンの電源アダプタは持ってくる(電池切れた…て人がちらほら)</li>
</ul>
</div>
</div>
<div class="p2">
<br /></div>
<div class="p1">
課題カードが無くなるの早かった割には、参加者少なかったんじゃないかな…<br />
あと提出方法、応募方法が分かりにくかったという声も…</div>
<div class="p2">
<a href="https://www.facebook.com/groups/salesforcedugjapan/" target="_blank">SDUG(Salesforce Developer User Group)</a>に参加してると最新情報に強くなれますよー、とちょっと宣伝で締めにします。</div>
<div class="p2">
<br /></div>
<br />
<div class="p2">
<br /></div>
pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-39469194576652903452012-12-04T00:00:00.000+09:002012-12-04T00:00:04.381+09:00[force.com] CSV添付のメールをスケジューリングして送る <p>
とある案件で毎日CSVをメールに添付して送る要件があり、実装できるまでの顛末のメモです。
問題は、添付するCSVの文字コードがShift_JIS、改行コードがCRLFじゃないといけないこと…
</p>
<h4>1. まずはControllerとVisualforceでSJISのCSVを吐き出してみる</h4>
<pre name="code" class="java">
public class CSVController {
public String getCRLF(){return '\r\n';}
public List<Account> accounts{get;set;}
public CSVController() {
accounts = [select id, name from Account];
}
}
</pre>
<pre name="code" class="xml">
<apex:page contentType="text/csv; charset=Shift_JIS" controller="CSVController">
<apex:repeat value="{!accounts}" var="a">{!a.id},{!a.name}{!CRLF}</apex:repeat>
</apex:page>
</pre>
<p>
こんな感じで、/apex/CSVPage でSJISのCSVがダウンロードできた。簡単すぎる。
</p>
<h4>2. メール添付して送ってみる</h4>
<p>
スケジュールApexでスケジューリングする予定なのでScheduableインタフェースを実装して、こんな感じで。
</p>
<pre name="code" class="java">
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});
}
}
</pre>
<p>
開発者コンソールで、new CSVMail().execute(null); とかすればメール送信成功!
あとはスケジュール設定するだけと思って「Apexをスケジュール」しても、エラーで送られないんですね…
</p>
<p>
<a href="http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_pages_pagereference.htm">PageReferenceのリファレンス</a>見てみると、「This method can't be used in: … Scheduled Apex …」って使えないじゃん…どうしよう。
</p>
<h4>3. 外部のサーバで文字コードだけ変換する</h4>
<p>
Apexで文字コード変換するのが簡単にはいかなさそうなので…外部サーバで文字コード変換する為だけのapiの様なものを置いて、コールアウトで使ってみる作戦。
</p>
<p>
ScalaのUnfilteredてフレームワークで、こんな感じでデプロイしてみた。<br>
※自分は某G社のクラウドに置きましたがherokuに置くとかいいですね!
</p>
<pre name="code" class="scala">
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))
}
}
</pre>
<p>
これを使って、Apex Classはこんな感じに。
実行前に「セキュリティのコントロール>リモートサイトの設定」で外部サーバのURLの追加も忘れずに。
</p>
<pre name="code" class="java">
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;
}
}
</pre>
<p>
これもまた、開発者コンソール等からの実行だとOKだけど、スケジュールするとコールアウトがダメ…
と思ったけど@future(callout=true)を付ければ非同期実行でコールアウト呼べることが判明したので@futureなメソッドにメール送信の処理を移動して完成!
</p>
<pre name="code" class="java">
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;
}
}
</pre>
pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-71594931999666798352011-12-18T01:05:00.004+09:002012-10-30T11:15:25.615+09:00Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方 (2)前回のサンプルアプリをちょっと時間が経ってから動かしてみると、<br />
<br />
<pre>[ERROR] In the error handler looking for a 401, and have a 401
[ERROR] Handleing the 401 error...
</pre><br />
とかエラーが出てしまい動きません…<br />
<br />
force.comからOAuth2で取得したaccess tokenには有効期限があるのでrefresh tokenを使って再度取得する必要があるのですが…そこが動作していないようです。<br />
※<a href="http://wiki.developerforce.com/page/JP:Digging_Deeper_into_OAuth_2.0_at_Salesforce.com" target="_blank">force.comのOAuthについて詳しくはこちらで</a><br />
<br />
エラーのコールバックも呼んでくれないので有効期限切れたかどうかも検出できず…<br />
<br />
なんとかhackできないかと、こんな感じでモジュールの中身をダンプしてみます。<br />
<br />
<pre name="code" class="javascript">var FDC = require('com.salesforce');
for (var i in FDC.ForceOAuth) {
Ti.API.debug(i + ':' + FDC.ForceOAuth[i]);
}
</pre><br />
最終的にREST APIの呼び出しはここに来るようです。<br />
<pre name="code" class="javascript">[DEBUG] makeRestCall:function (path, callback, error, method, payload, retry) {var restUrl=Ti.Network.decodeURIComponent(fa.instanceUrl)+'/services/data'+path;var xhr=Ti.Network.createHTTPClient();xhr.onload=function(){Ti.API.info("REST Response: "+this.responseText);var data="";if(this.responseText){data=this.responseText;}
callback(data);};xhr.onerror=function(e){Ti.API.error("XHR, error handler..."+"\nDbDotCom.REST.OAuth.refreshToken: "+fa.refreshToken+"\nretry: "+retry+"\n e: "+e.error+"\nXHR status: "+this.status);if(!fa.refreshToken||retry){error(e.error);}else{Ti.API.error("In the error handler looking for a 401, and have a "+xhr.status);if(xhr.status===401){Ti.API.error("Handleing the 401 error...");exports.refreshAccessToken(function(oauthResponse){Ti.API.error("Refresh response... "+oauthResponse);fa.makeRestCall(path,callback,error,method,payload,true);},error);}else{Ti.API.error("Not a 401 error, re-throwing...");error(e);}}};if(fa.usePostBin===true){restUrl="http://www.postbin.org/135onm5";}
xhr.open(method||"GET",restUrl,true)
Ti.API.info("Rest url: "+restUrl);xhr.setRequestHeader("Authorization","OAuth "+Ti.Network.decodeURIComponent(fa.accessToken));xhr.setRequestHeader("Content-Type","application/json");xhr.send(payload);}
</pre><br />
出てるログから、exports.refreshAccessToken()の呼び出しの中でエラーが起きてコールバックまで戻って来ないようです。<br />
<br />
<strike>よく考えたら、<b>OAuth2のrefresh tokenによるaccess token再取得時にはclient secretが必要</b>なはずなのに、モジュールのパラメータなどでどこにもセットしていないのでrefreshできるわけないですね… 未実装なんでしょうか?<br /></strike>
※追記:secret要らない仕様に変わってました
<br />
ダンプしたソースを参考に、<a href="https://gist.github.com/1490519" target="_blank">こんな感じ</a>でhackしてみました。<br />
※モジュール内で定義されてるobjectのプロパティは動的に書き換えられない?&スコープ的にアクセスできない変数があったので結構無理矢理<br />
<br />
使い方は、requireした後にこのファイルをincludeしてパッチを当て、ForceOAuthの代わりにForceOAuth2を使うようにします。ForceOAuth2.openのパラメータにはclient id<strike>とclient secret</strike>を渡す様にします。<br />
<br />
<pre name="code" class="javascript">var FDC = require('com.salesforce');
Ti.include('fdc-patch.js');
FDC.ForceOAuth2.open('CLIENT_ID');</pre><br />
パッチしたポイントは2つ。refreshAccessTokenを<strike>client secretを使用して</strike>動作する様にしたのと、<b>REST APIのパスの固定部分に /data が含まれていたのを /apexrest が呼べる様に /services までとした</b>こと。<br />
<br />
Winter '12でリリースされたApex RESTを使って公開したAPIのURLは、$instance_url/services/apexrest/... となるので、FDC.ForceOAuth2.makeRestCall('/apexrest/myapi', callback) の様な感じで使える様になります。<br />
<br />
<a href="https://marketplace.appcelerator.com/apps/836#support" target="_blank">marketplaceのモジュールのページ</a>には、"This toolkit is maintained by the community and sponsored by salesforce.com. Salesforce.com does not officially support this product."とか書いてあるんですが、<b>パッチとか提供したい場合どこに連絡すればいいんでしょうか</b>… githubとかにソース上がってればforkするのに…<br />
<br />
この記事は<a href="http://atnd.org/events/22909" target="_blank">Force.com Advent Calendar 2011</a>に参加しています。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com6tag:blogger.com,1999:blog-7854445846272488112.post-6529114071859179582011-12-08T02:31:00.000+09:002011-12-08T02:31:56.923+09:00Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方最近、Titanium Mobileでforce.comのREST APIを使用したiPhoneアプリを作ってます。<br />
<br />
Titanium Mobileについての基本的な知識やTitanium Studioの使い方はある程度知ってる前提で書いているので、Titaniumについて知りたい方は「<a href="http://gihyo.jp/dev/serial/01/titanium/0001" target="_blank">Titanium Mobileで作る! iPhone/Androidアプリ</a>」などを参考にどうぞ。<br />
<br />
<a href="https://marketplace.appcelerator.com/" target="_blank">Titaniumにはmarketplace</a>があり、ソフトウェア部品としてのモジュールや、アプリのテンプレートなどをダウンロードできます(有償のもアリ)。<br />
うまいこと目的に合ったモジュールを見つけて利用すれば開発スピードは向上する…はず…<br />
<br />
<a href="https://marketplace.appcelerator.com/apps/836" target="_blank">Salesforce用のモジュール</a>も用意されているので、今回の開発にはこれを利用しました。が、ドキュメントが大して無かったりで取っ掛かり苦労したのでメモとして残しておきます。<br />
<div class="p1">今回は、モジュールを使うまでの準備と、サンプルコードの実行まで。<br />
<a name='more'></a></div><br />
<h4><span class="Apple-style-span" style="font-size: large;">まずはモジュールのダウンロードと設定</span></h4><div class="p3"><br />
marketplaceへログイン後、<a href="https://marketplace.appcelerator.com/apps/836">https://marketplace.appcelerator.com/apps/836</a>からSalesforce用モジュールをダウンロードすることができます。<br />
<br />
ダウンロードしたzipを解凍すると、<br />
<br />
<ul><li>com.salesforce-iphone-1.0.zip</li>
<li>documentation/</li>
<li>example/</li>
<li>screenshots/</li>
</ul><br />
の4つが入ってるはず。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0hiOHuIOpM6F9gPzSzJHwWH-KDKxfDrKdnTYFpqgxJpq2y2IhzB5nDSoFt-HBK9wTegHYYt2bVgLjn2AfP1Wg-zWI6kQFqF0OHshY_vc5DStVldoZX0HhMeWt54KcJQZ8X6WqLHna2u0/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+0.59.16%25EF%25BC%2589.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0hiOHuIOpM6F9gPzSzJHwWH-KDKxfDrKdnTYFpqgxJpq2y2IhzB5nDSoFt-HBK9wTegHYYt2bVgLjn2AfP1Wg-zWI6kQFqF0OHshY_vc5DStVldoZX0HhMeWt54KcJQZ8X6WqLHna2u0/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+0.59.16%25EF%25BC%2589.png" width="203" /></a>Titanium Studioで新規プロジェクトを作り、<b>プロジェクトのルートディレクトリへ上記com.salesforce-iphone-1.0.zipをコピー</b>します。<br />
<br />
とりあえず1回▶ボタンを押してビルドすると、zipファイルが消え、modulesフォルダが展開されます。<br />
<br />
iPhoneシミュレータが起動しますが一旦閉じます。<br />
<br />
tiapp.xmlの一番下、<modules/>となっているところを<br />
<pre class="xml" name="code"><modules>
<module version="1.0">com.salesforce</module>
</modules>
</pre> に書き換えます。これ結構忘れる。Titanium Studioが自動でやってくれるといいのに。<br />
<br />
これでモジュールを使う準備は完了です。<br />
<br />
<h4><span class="Apple-style-span" style="font-size: large;">consumer key/secretの準備</span></h4><div>force.comへの認証はOAuth 2.0を使用するのでconsumer key/secretの取得が必要です。</div><br />
force.com環境にログインし、<b>設定メニューから 開発 > リモートアクセス を選択</b>します。<br />
コールバックURLは使わないので適当に。必須項目を適当に埋めて下さい。<br />
<br />
保存すると、コンシューマ鍵が表示されるのでこれを使用します。<br />
コンシューマの秘密(consumer secret)は今回は使いませんが後で使います。<br />
※コンシューマの秘密、とか翻訳が面白いですねw<br />
<br />
<br />
<h4><span class="Apple-style-span" style="font-size: large;">サンプルコードの実行</span></h4>ダウンロードしたzipに入っていた、example/app.jsをResources/app.jsに上書きコピーします。<br />
<br />
実は<b>exampleで付いてくるapp.jsこのままだとバグってる</b>ので…、コールバックメソッドの先頭に<br />
e = JSON.parse(e); <br />
を入れてやります。<br />
<a href="https://gist.github.com/1443640" target="_blank">上記追加済みのapp.jsはgistに上げました</a>。<br />
※コールバックメソッドに戻ってくるのはJSON文字列なのにそのままobjectとして使おうとしてる…<br />
<div><br />
</div>app.jsの下の方、<b>FDC.ForceOAuth.openの引数に先ほど発行したコンシューマ鍵を渡します</b>。<br />
<br />
実行してみます。<br />
※エラーが出る場合は、buildディレクトリに古いのが残ってて差分ビルドがうまくいかない?ことがあるので Project > Clean すると治ると思います。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh377Y-yTWjV1jDUi1eApgu7R28MENnwiDNfreiKDyAr1oxBp6SjMsYqaiEWHU0SEKzUR-OvFbEYCxZxxKPDDpRJ2boZUk5_mfGXfIVfUp4X-oL4iO0wgX5FTeO7eGf5fdZYpjSL7oJ0lc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+1.28.31%25EF%25BC%2589.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh377Y-yTWjV1jDUi1eApgu7R28MENnwiDNfreiKDyAr1oxBp6SjMsYqaiEWHU0SEKzUR-OvFbEYCxZxxKPDDpRJ2boZUk5_mfGXfIVfUp4X-oL4iO0wgX5FTeO7eGf5fdZYpjSL7oJ0lc/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+1.28.31%25EF%25BC%2589.png" width="170" /></a>Salesforceへのログイン画面が出て、ログインして許可ボタンを押すと、こんな画面になります。<br />
<br />
Query User〜Describe User辺りのボタンを押すとそれぞれREST APIがコールされて結果がTableViewで表示されます。<br />
レスポンスJSONの内容はコンソールにも出力されるのでこれを参考にしてJSON.parseした結果を自由に扱えますね。<br />
<br />
Create Record以下のボタンはカスタムオブジェクトを先に作っておかないとエラーになりますので気になる人はサンプルコードを参考に作ってみると試せるかも。<br />
<br />
<br />
<br />
実は<b>Apex RESTで公開したAPIが呼べなかったり、OAuth Tokenの有効期限が切れてしまうとエラーになったり</b>と穴があるのですが…この辺りの対策は次回書く予定です(どうやらAdvent Calendarが2周目も回ってくる様なので…)。<br />
<br />
この記事は<a href="http://atnd.org/events/22909" target="_blank">Force.com Advent Calendar 2011</a>に参加しています。</div>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-23743497843550608742011-09-20T08:47:00.003+09:002011-09-20T08:50:34.880+09:00foursquare global hackathon @ TokyoNYC, SF, Paris, Tokyoで同時開催の<a href="https://github.com/foursquare/hackathon/wiki/Foursquare-Global-Hackathon">foursquare global hackathon</a>、東京会場のオーガナイザ兼参加者として9/17-18の2日間約42時間ぶっつづけで楽しんできました。<br />
<br />
東京会場では参加者15名、応募作品9と他会場と比べて少ない数字でしたが、みんなでワイワイおやつ食べたり深夜居残り組はみんなで銭湯行ったり、いい雰囲気でした。<br />
NYC,SF,Parisの中継映像をスクリーンに映して、音声無しながらもParisがエッフェル塔切り抜いたのカメラの前に貼って遊んでたのに東京タワーで対抗?してみたりw<br />
<a href="http://togetter.com/li/189663">Togetterまとめ</a><br />
<br />
いきなりですが東京会場内での作品の投票結果発表!<br />
<i><span class="Apple-style-span" style="font-size: x-large;">日本時間9/23の正午まで、<a href="http://fshackathon.appspot.com/">http://fshackathon.appspot.com/</a>で一般投票が行われているので気に入った作品にぜひ投票をお願いします(何作品でも投票できます)!</span></i><br />
<br />
<b>1位(会場得票数4): photosquare</b><br />
作者:中継カメラ設置等いろいろ手伝ってくれた<a href="http://twitter.com/koogawa">@koogawa</a>さん<br />
内容:近くのvenueに投稿されてる写真をスライドショー表示してくれるiPhone/iPad用アプリ。アプリ審査があるのでまだダウンロードできませんが、<a href="http://twitvideo.jp/06TpC">http://twitvideo.jp/06TpC</a>から映像見ることができます。<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNQPDA">投票する</a><br />
<br />
<b>2位(会場得票数3):The Journalist</b><br />
作者:今回素晴らしい会場を提供してくださったGaiaXの<a href="http://twitter.com/aomushi510">@aomushi510</a>さんと、直前に@aomushi510さんに誘われて急遽参加のTomoyuki Hisadaさんのチーム<br />
内容:4sqのチェックイン履歴を使用して、自分の行った場所の記事を投稿できる、ソーシャルニュース的なサービス。新聞的なデザインや、細かいところの動きがよくできていて完成度高くてびっくり!<br />
<a href="http://journalist.azu.sh/">http://journalist.azu.sh/</a>で見れる…はずが残念ながら今エラー出てます…<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGLoXDA">投票する</a><br />
<br />
<b>同着2位(会場得票数3):4sqlist(仮)</b><br />
作者:pomu0325(すいません自分です。)<br />
内容:サブベニュー(駅のホームとか、モール内の店舗とか)をグループ化して探しやすくするiPhone/iPad用WEBアプリ。時間が無くて最低限の機能しか作り込めませんでした… 同じアイデアで作り始めた@ikawamotoさんとは違い、手動でサブベニューの情報を編集するアプローチです。<a href="http://4sqlist.appspot.com/">http://4sqlist.appspot.com/</a><br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGKEfDA">投票する</a><br />
<br />
以下、順不動での掲載です。<br />
<br />
<b>チーム名:curly</b><br />
作者:@takochuuさん、@sunny_510さんのチーム<br />
内容:venue毎に、そこにチェックインした人のつぶやきが見えたりランキングが見れたりするサービス。デザインもよくまとまってましたが、公開はまだ…かな?<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNIPDA">投票する</a><br />
<br />
<b>Packed Venues</b><br />
作者:大阪から来てくれた@ikawamotoさん<br />
内容:住所が同じベニューをまとめて表示するiPhoneアプリ。<a href="http://www.youtube.com/watch?v=t3ZSdOBYiXs">動かしているところの動画はこちら</a>。住所表記のブレが解決できれば自動でまとめられるアプローチの方がいいですね!<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNsPDA">投票する</a><br />
<br />
<b> Squarecount </b><br />
作者:meetup主催等いつも手伝ってくれてる@jayjpn<br />
内容:4sqへのチェックイン回数を表示するだけw。エンジニアじゃないのに締め切りぎりぎりまで頑張ってました。<a href="http://squarecount.appspot.com/index.html">http://squarecount.appspot.com/index.html</a><br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNUPDA">投票する</a><br />
<br />
<b>4sq Scouter</b><br />
作者:自分と同じく携帯用4sq「じゃぽすく」を作っている@jpfoursquareさん<br />
内容:スカウターと言ったらおなじみ、例のアレ風に「強さ」を数値化して表示してくれます。数値には、「複雑な足し算」が使用されているとのことですw <a href="http://scouter.jpfoursquare.com/">http://scouter.jpfoursquare.com/</a>から試せます。<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGLkXDA">投票する</a><br />
<br />
<b>fourSquare PopUp Utility</b><br />
作者:The Journalistもチームメンバーで作っているTomoyuki Hisadaさん<br />
内容:4sqのユーザやvenueの情報をマウスオーバーで表示してくれるjsライブラリ。ページ内のfoursquareへのリンクを自動で変換してくれるみたいです。twitter anywhereの4sq版、と言えば分かる人には分かる。<a href="http://lab1.anotherbrain.co.jp/fsht/popup.html">使い方等</a><br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGPEuDA">投票する</a><br />
<br />
<b>Complete Japanese fried chicken delis in MusashiKoyama</b><br />
作者:@Suger1008 さん<br />
内容:むさこ(武蔵小山)の唐揚げ屋さんを制覇! むさこ愛にあふれるスマートフォン用アプリをTitanium Mobileで作られていました。<a href="http://musakoapp.blogspot.com/2011/09/complete-japanese-fried-chicken.html">公開はまだですが解説等はこちら</a>。<br />
<a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGPEuDA">投票する</a><br />
<br />
応募があったのは以上9作品ですが、他にも時間が足りなかったり、既に作っているものを4sq対応したりと各自ガリガリがんばっていました。<br />
<br />
最後に、深夜含め2日連続と無理な条件にも関わらずすてきな会場を提供してくださった、<a href="http://www.gaiax.co.jp/">ガイアックス</a>さん、本当にありがとうございました!!!<br />
ハッカソンなどのイベントでよく会場提供をしているそうなので、またお邪魔させていただきます!pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-5282619026246256932011-03-29T19:34:00.001+09:002011-03-29T19:34:58.061+09:00[Lift] 画像ファイルにjsessionidを付けない<a href="http://blog.takeda-soft.jp/blog/show/384">takedasoftさんがUrl Rewrite Filterを使った方法を書いてました</a>が、Boot.scalaでLiftRule.urlDecorateにルール追加するだけで簡単にできたのでメモ。<br />
<br />
<pre name="code" class="scala">val ImgPtn = """(.*\.(?:png|gif|jpg));jsessionid=.+""".r
LiftRules.urlDecorate.append {
case ImgPtn(url) => url
}
</pre><br />
※Lift 2.2から<a href="http://pomu0325.blogspot.com/2010/12/lift-lift22cookiejsessionid.html">デフォルトではjsessionid付かなくなったのはここに書いてある通り</a>です。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-62931047031603608102011-03-22T13:55:00.000+09:002011-03-22T13:55:45.827+09:00[Scala] sbt jetty-runでhttpsを使用したい<h3>方法1: カスタムjetty.xmlを使う</h3>sbt標準だとjetty.xmlを1から書けばOK。<br />
<pre class="scala" name="code">override def jettyConfiguration = new CustomJettyConfiguration {
override def jettyConfigurationXML = List("/path/to/jetty.xml")
}
</pre><br />
この方法だとjetty.xmlにConnectorとかHandlerとか全部自分で書かないといけないので面倒…<br />
参考:<a href="http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax">jetty.xmlのリファレンス</a><br />
<br />
<h3>方法2: sbtにちょっと手を入れてjetty設定用のhookを使う</h3><h4>sbtのソースをcheckoutしてhookが設定できるようにする<br />
</h4><br />
参考:<a href="http://code.google.com/p/simple-build-tool/wiki/Build">Build - simple-build-tool -</a><br />
<br />
sbtのソースをcheckout<br />
<pre class="scala" name="code">$ git clone git://github.com/harrah/xsbt.git
$ cd xsbt
</pre><br />
変更したのは下の2行だけ。jetty-runが呼ばれてデフォルトのhttpポートのConnectorの設定直後にcallbackで設定した関数が呼ばれるようにする。<br />
<pre class="scala" name="code">diff --git a/sbt/src/main/scala/sbt/WebApp.scala b/sbt/src/main/scala/sbt/WebApp.scala
index f5a3d09..997294e 100644
--- a/sbt/src/main/scala/sbt/WebApp.scala
+++ b/sbt/src/main/scala/sbt/WebApp.scala
@@ -105,6 +105,8 @@ trait DefaultJettyConfiguration extends JettyConfiguration
def parentLoader: ClassLoader
def jettyEnv: Option[File]
def webDefaultXml: Option[File]
+
+ def callback: Option[AnyRef => Any] = None
}
abstract class CustomJettyConfiguration extends JettyConfiguration
{
diff --git a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
index 263afa6..4ca4b0e 100644
--- a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
+++ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
@@ -54,6 +54,7 @@ private object LazyJettyRun${jetty.version} extends JettyRun
case c: DefaultJettyConfiguration =>
import c._
configureDefaultConnector(server, port)
+ c.callback.foreach(_(server))
val webapp = new WebAppContext(war.absolutePath, contextPath)
webDefaultXml.foreach{webDefaultXml:File => webapp.setDefaultsDescriptor(webDefaultXml.toString)}
</pre><br />
<h4>sbtのビルド</h4><pre name="code" class="scala">$ sbt update generate-loader-compat proguard "project Simple Build Tool" publish-local
</pre>※generate-loader-compatしないとjetty6用のクラスが一部生成されなかった。(jettyは6->7でパッケージ名が変わったので、<a href="https://github.com/harrah/xsbt/tree/master/sbt/src/main/scala/sbt/jetty">sbtのソースではテンプレート化して6,7,7.2用のscalaソースが吐かれるようになってる</a>)<br />
<br />
<h4>ssl用証明書の準備</h4>JDKの<a href="http://java.sun.com/j2se/1.5.0/ja/docs/ja/tooldocs/solaris/keytool.html#selfcertCmd">keytool</a>を使用。とりあえずオレオレ証明書で。<br />
<pre name="code" class="scala">$ keytool -keystore src/test/resources/keystore -alias jetty -genkey -keyalg RSA
$ keytool -selfcert -validity 1024 -keystore src/test/resources/keystore -alias jetty
</pre>※keystoreのパスワードを聞かれるので入力します<br />
<br />
<h4>sbt プロジェクト側の準備</h4>build.propertiesの sbt.version=0.7.5.RC0 にしてsbt実行、先に標準の0.7.5.RC0を入れておく。<br />
<br />
ビルドしたsbt/target/scala_2.7.7/sbt_2.7.7-0.7.5.RC0.jar をプロジェクトのproject/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.5.RC0/sbt_2.7.7-0.7.5.RC0.jar に上書き。<br />
<br />
project/build/MyProject.scala の設定。callbackでjettyのServerインスタンスにSslSocketConnectorを追加。ClassLoaderが違うので普通に書くとClassCastExceptionが発生するのでリフレクションで。<br />
<pre name="code" class="scala"> override def jettyConfiguration: JettyConfiguration =
new DefaultJettyConfiguration {
def classpath = jettyRunClasspath
def jettyClasspath = MyProject.this.jettyClasspath
def war = jettyWebappPath
def contextPath = jettyContextPath
def classpathName = "test"
def parentLoader = buildScalaInstance.loader
def scanDirectories = Path.getFiles(MyProject.this.scanDirectories).toSeq
def scanInterval = MyProject.this.scanInterval
def port = jettyPort
def log = MyProject.this.log
def jettyEnv = jettyEnvXml
def webDefaultXml = jettyWebDefaultXml
override def callback = Some((server: AnyRef) => {
val cl = server.getClass.getClassLoader
val ssl = cl.loadClass("org.mortbay.jetty.security.SslSocketConnector").newInstance.asInstanceOf[{
def setPort(p: Int): Unit
def setMaxIdleTime(t: Int): Unit
def setKeystore(s: String): Unit
def setPassword(s: String): Unit
def setKeyPassword(s: String): Unit
def setTruststore(s: String): Unit
def setTrustPassword(s: String): Unit
}]
val addConnector: java.lang.reflect.Method = server.getClass.getMethod("addConnector", cl.loadClass("org.mortbay.jetty.Connector"))
ssl.setPort(8443)
ssl.setMaxIdleTime(30000)
ssl.setKeystore(info.projectPath + "/src/test/resources/keystore")
ssl.setPassword("password")
ssl.setKeyPassword("password")
ssl.setTruststore(info.projectPath + "/src/test/resources/keystore")
ssl.setTrustPassword("password")
addConnector.invoke(server, ssl)
})
}
</pre>※上記はjetty6の場合で、jetty7の場合はパッケージ・クラス名がちょっと違います。<br />
<br />
これで、jetty-runで8080でhttp、8443でhttpsが起動します。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-31966619695177793092011-02-09T18:55:00.000+09:002011-02-09T18:55:04.728+09:00[Scala][sbt]DefaultWebProjectでwarではなくjarも作りたいDefaultWebProjectだとsbt packageタスクでwarが作られますが、jarも作りたい時があった場合、こんな感じで。<br />
<pre name="code" class="scala">lazy val jar = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) describedAs "Creates a jar file."
</pre>sbt jarでjarファイルが作られます。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-1203014906319944972011-02-07T21:19:00.010+09:002011-03-22T13:07:40.511+09:00[GAE] bulkloaderをGAE/Jで使うpython版appengine-sdkを使ってcsvファイルからデータをDatastoreにロードすることができます。<br />
<br />
<h3>準備</h3><ul><li><a href="http://code.google.com/appengine/downloads.html">python版appengine-sdk</a>をインストールする<br />
</ul><h3>web.xmlにremote_api用のServletの設定を書く</h3><pre name="code" class="xml"><servlet>
<servlet-name>remoteapi</servlet-name>
<servlet-class>com.google.apphosting.utils.remoteapi.RemoteApiServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>remoteapi</servlet-name>
<url-pattern>/remote_api</url-pattern>
</servlet-mapping>
</pre>security-constraintも追加しておいた方が良さげ。 <pre name="code" class="xml"><security-constraint>
<web-resource-collection>
<url-pattern>/remote_api</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
</pre><h3>ロード設定用のyamlファイルを書く</h3><pre name="code" class="js">python_preamble:
- import: base64
- import: re
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.bulkload.bulkloader_wizard
- import: google.appengine.api.datastore
- import: google.appengine.api.users
transformers:
- kind: Scores
connector: csv
connector_options:
encoding: ms932
property_map:
- property: __key__
external_name: id
import_transform: int
- property: name
external_name: name
- property: score
external_name: score
import_transform: transform.none_if_empty(float)
</pre><li>キーにidではなく文字列(name)を使用する場合は、import_transform: intは不要。<br />
<li>import_transformに他に指定できる例<br />
日付: transform.import_date_time('%Y/%m/%d %H:%M')<br />
固定値: import_transform: lambda x:1<br />
数値変換: import_transform: lambda x:int(x)*100<br />
※lambdaは引数の:の後にスペースを空けるとダメな様です<br />
<br />
ロード用CSV<br />
<pre name="code" class="js">id,name,score
1,Alice,5.0
2,Bob,2.5
3,Caroline,
</pre><br />
<h3>ロード用コマンド</h3><pre name="code" class="js">appcfg.py upload_data --filename=test.csv --config_file=test.yml --url=http://{appid}.appspot.com/remote_api --application={appid} --kind=Scores -v
</pre>※{appid}はアプリケーションIDに置き換える<br />
<br />
security-constraintの設定をしている場合、毎回管理者アドレス/パスワードの入力が必要です。<br />
ローカルのdev_appserverだとエラーが出て動きませんでした…<br />
<br />
こんな感じに反映されます。<br />
<div class="separator"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKhSZ__EBYNTkyL9Km_F0Aa8TW7XUCHLoit9L_CC3gDVF-RTgfAfbDEb4FBm33DUFTFZq67mVIF2zLnuxkeiMxFKnJjwEc4giUNqVYfhAHRWyJR9vusH46VABodgUezz7v-AFmVlI-6LE/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-02-07+21.11.55%25EF%25BC%2589.png" imageanchor="1"><img border="0" height="122" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKhSZ__EBYNTkyL9Km_F0Aa8TW7XUCHLoit9L_CC3gDVF-RTgfAfbDEb4FBm33DUFTFZq67mVIF2zLnuxkeiMxFKnJjwEc4giUNqVYfhAHRWyJR9vusH46VABodgUezz7v-AFmVlI-6LE/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-02-07+21.11.55%25EF%25BC%2589.png" /></a></div>※1万件だと約500秒(8分ちょっと)くらいかかりました(CPU Timeを無料Quotaの10%ほど消費します)<br />
<br />
<h3>その他注意点</h3><strike><li>slim3を使っている場合、filterの設定があると、"ProtocolBufferDecodeError: corrupted"の様なエラーが出てしまうので一時的にコメントアウトして回避しました。(※他にいい方法があれば教えて下さい…</strike><br />
<br />
<strike>2/11追記: bulkload用にfilterをコメントアウトしたversionをデプロイしておいて、--url=http://latest.bulkload.{appid}.appspot.com/remote_api みたいに指定するといいかも</strike><br />
<br />
3/22追記:slim3ではなくscenic3の方の設定でした。matcherで/remote_apiを除外したらOK。<br />
<br />
参考: <a href="http://ikaisays.com/2010/06/10/using-the-bulkloader-with-java-app-engine/">Ikai Lan says / Using the bulkloader with Java App Engine</a>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-22795619793903580572011-01-15T11:02:00.001+09:002011-01-15T11:02:46.342+09:00[Blogger] スマートフォン用テンプレートを有効にする<a href="http://bloggerindraft.blogspot.com/2010/12/new-mobile-templates-for-reading-on-go.html">いつの間にかβ版としてスマートフォン用テンプレートの機能が追加されてた</a>ので有効にしてみた。<br />
<br />
draft.blogger.com の方からログインするとβ版機能が使えます。<br />
<br />
有効にするには、設定 > メールとモバイル > モバイル テンプレートを表示する で はい を選ぶだけ。スマートフォンの場合は自動でリダイレクトしてくれるようです。<br />
<br />
ガラケーからもurlの最後に?m=1を付けるとモバイル用テンプレートで表示してくれましたが、ページ遷移にjavascriptが使われているようで残念ながら記事一覧以降進めませんでした…。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-5400019329764138542011-01-12T16:04:00.004+09:002011-02-07T21:23:58.708+09:00[Lift] Liftで処理したくないリクエストの設定web.xmlで全てのリクエストがLiftで処理されるようになっているので、Lift以外のサーブレット等で処理したい場合に困ります…<br />
<pre class="xml" name="code"><filter-mapping>
<filter-name>LiftFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</pre><br />
Boot.scalaのdef bootにこんな感じで書けばOK。<br />
<pre class="scala" name="code">LiftRules.passNotFoundToChain = true
LiftRules.liftRequest.append {
case Req("_ah" :: _, _, _) => false
case Req("remote_api" :: _, _, _) => false
}
</pre><br />
※filter-mappingで除外ルールが書ければいいんだけど…pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-9330061225921399242011-01-01T18:50:00.005+09:002011-01-01T20:19:39.562+09:00[Scala] sbt-appengineなプロジェクトをIntelliJ IDEAでデバッグ<a href="http://twitter.com/#!/teaplanet/status/20848674703675392">@teaplanetさんにヒントいただいてIDEAのsbt consoleでdev-appserver-startできるようになった</a>ので、IDEA上でブレークポイントで止めたりできるように設定してみた。<br />
<br />
<pre name="code" class="scala">class MyProject(info: ProjectInfo) extends AppengineProject(info) {
//... 中略
//APPENGINE_SDK_HOME設定の代わり
override val appengineSdkPath = Path.fromFile("/Users/pomu0325/dev/appengine-java-sdk-1.4.0")
//dev-appserver-startの際のJVMオプション
override val devAppserverJvmOptions = List("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,address=2011,suspend=y") ++ super.devAppserverJvmOptions
}
</pre><br />
この設定でsbt consoleからdev-appserver-startするとリモートデバッガで接続できる状態で起動するので、後はIDEAからデバッガ接続する。<br />
<br />
<div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivUZIm1DWhjDYldByZW5F8v2fe2lLeRitz5kcvlkunu7bd-IpoNieQLzo4wkRyVh-aYYAqZkl8J3VPBwaktnXRxQoqvJtC44iSQ2X69Z2IBwm3HLTbQ0LaYf6ACDMeQUt12JjX1twitNo/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-01-01+18.45.38%25EF%25BC%2589.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"><img border="0" height="298" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivUZIm1DWhjDYldByZW5F8v2fe2lLeRitz5kcvlkunu7bd-IpoNieQLzo4wkRyVh-aYYAqZkl8J3VPBwaktnXRxQoqvJtC44iSQ2X69Z2IBwm3HLTbQ0LaYf6ACDMeQUt12JjX1twitNo/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-01-01+18.45.38%25EF%25BC%2589.png" /></a>IDEAのメニューからRun -> Edit Configurations -> [+] -> Remote を選んで、Nameは適当に、PortにdevAppserverJvmOptionsのaddress=xxxxで指定したポートを入れる(↑の例だと2011。適当に開いてるポート設定すればOK)。<br />
</div><br />
設定したら、Run -> Debug... で選んでデバッグ開始するとdev-appserverのJVMにアタッチされてブレークポイントで止まってくれる。<br />
<br />
※Before launchの"Run sbt action"でdev-appserver-startが指定できればsbt consoleからいちいちdev-appserver-startしなくても一発で起動できそうだけど一覧に出てこないし、直接指定してもダメでした…<br />
<br />
参考:sbt-appengineの基本的な設定等は"<a href="http://kaitenn.blogspot.com/2010/10/sbt-sbtgae.html">sbtでGAEをする場合の注意点など色々。</a>"が参考になります。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-55757184055059119182010-12-31T17:00:00.000+09:002010-12-31T17:00:22.220+09:00[Lift] Lift2.2でCookieが無い場合にjsessionidがリンクに付加されない<a href="http://www.assembla.com/spaces/liftweb/tickets/658">#658 Supressing URL rewriting for jesessionid from Lift</a> の修正により、デフォルトでjsessionidが付かないように動作が変わっています。<br />
<br />
Boot.scalaで、<br />
<pre name="code" class="scala">LiftRules.encodeJSessionIdInUrl_? = true
</pre>してやればOK。<br />
<br />
Liftで携帯サイト作ってる人とかはアップデートの際に要注意!pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-87603726323938636112010-12-30T16:53:00.002+09:002010-12-30T17:00:24.578+09:00[Scala] dispatchでContent-Length付きのmultipartなPOSTする方法とある写真投稿サービスのAPIにdispatch(0.7.8)を使って実装しようとした時のこと。APIのレスポンスでContent-Lengthが無いって怒られました。<br />
<br />
dispatch-mimeを使うとmultipart/form-data形式でPOSTができるのですが、普通に実装するとContent-Lengthを送ってくれないようです。<br />
<br />
<a href="http://sourced.implicit.ly/net.databinder/dispatch-mime/0.7.8/dispatch/Mime.scala.html">ソースコメント</a>にも書いてあるし。<br />
<blockquote>Note that when using an InputStream generator, chuncked encoding will be used with no Content-Length header <br />
</blockquote><br />
Fileが引数の方を使えば良さげだけどGAEからだとFile使えません。dispatchのソースとapache-httpclientのjavadoc眺めて工夫したらできました。<br />
<br />
<pre name="code" class="scala">import dispatch._
import dispatch.Http._
import dispatch.mime.Mime._
import org.apache.http.entity.mime.content.ByteArrayBody
var req = ... //普通にdispatchのRequest作る
req = req next req.add(paramName, new ByteArrayBody(data, contentType, filename)) //ByteArrayBodyを指定
</pre><br />
※ただし、<a href="http://hc.apache.org/httpcomponents-client-dev/httpmime/apidocs/org/apache/http/entity/mime/content/ByteArrayBody.html">ByteArrayBody</a>はhttpclient 4.1(現在BETA1)から追加されるクラスなので4.1のjarを使わないとダメ(dispatch 0.7.8のdependencyはhttpclient 4.0になってた)。<br />
sbtならこんな感じで。<br />
<pre name="code" class="scala">val httpmime = "org.apache.httpcomponents" % "httpmime" % "4.1-beta1"
</pre>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-23713028531406954952010-09-06T01:20:00.000+09:002010-09-06T01:20:49.318+09:00[Lift][GAE] Scala座01でLTしてきました<a href="http://scalaza.com/ScalaZa01/">第1回Scala座</a>に参加してきました。実はスタッフとして少々お手伝いもしたのですが大した事ないので(ry<br />
<br />
収穫は…<br />
<ul><li>いろいろと勉強不足だなぁと刺激を受けた(FP的な意味で)</li>
<li>Twitter等で気になっていた人々にリアルで会えた</li>
<li>ワラビモチ!←マイブーム</li>
</ul>といったとこでしょうか。<br />
<br />
「Lift on GAE/J」ネタでLTさせてもらったのですが、先にまとめてしまうと、「LiftはGoogle AppEngineには向いてない」てことです。詳しくは<a href="http://www.slideshare.net/pomu0325/lift-on-gaej">「Lift on GAE/J」のスライド</a>見てください。<br />
<br />
LTやろうと思ったきっかけは、自分でLiftも(Scalaも)GAEもよく分かってないうちにLift+GAEという組み合わせを選んでしまったのですが、いろいろと苦労したので… 最近TwitterでLiftをGAEで動かしてみるとかのtweetを時々見かけ、危険だよー、ということを広めたかっただけです。<br />
<br />
ただ、勘違いしないで欲しいのは、Liftがダメと言っている訳ではなく、LiftはフルスタックのWebフレームワークとして、慣れれば生産性よくAJAX/Cometを使ったリッチなWebアプリを作れる素晴らしいフレームワークだと思っています。クラウドという性質上、GAEには様々な制限があるので、GAE上で動かしたいのであれば他のもう少し軽量なフレームワークが向いていると思います(まだあまり試せていませんが…)。<br />
<br />
Scala座の企画をして下さった<a href="http://twitter.com/keisuke_n">@keisuke_n</a>さん、Scalaを勉強していく上で参考にさせていただいたり助けていただいたりで本当にいろいろとお世話になっている<a href="http://twitter.com/yuroyoro">@yuroyoro</a>さん、<a href="http://twitter.com/kmizu">@kmizu</a>さん、<a href="http://twitter.com/takedasoft">@takedasoft</a>さん、会場・懇親会でお話させていただいたみなさま、本当にありがとうございました。ぜひ第2回でもよろしくお願いします!pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-79402885567240839892010-05-15T22:41:00.004+09:002011-07-04T17:54:18.375+09:00[OAuth] 携帯でTwitterのOAuthを無理やりやってみた<b>2011-07-04追記: 今はTwitterのOAuth画面もガラケー対応されてます。自動でリダイレクトはされないのでユーザによるクリックは相変わらず必要ですが。</b><br />
<br />
<a href="http://www.mb4sq.jp/">モバイルフォースクエア</a>の開発初期に、OAuthでTwitterの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。<br />
<h4>AU(W61CA)でTwitterのOAuthをやってみた</h4><table><tbody>
<tr><th>ログイン画面</th> <th>承認画面</th> </tr>
<tr> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBx8i_6vcecWYc-5rg3gipkk_Ay3r2jPcyEymDCZDFKxeknz-ATV07FakL7FWV06WokKhowMhbxxGsqOFWm0p-vV8KTr3trd17Lo9HgtgIjIg-N4PSisnjEBnja50j_KbRDUvbJW0VYaw/s1600/w61ca-tw-login2.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBx8i_6vcecWYc-5rg3gipkk_Ay3r2jPcyEymDCZDFKxeknz-ATV07FakL7FWV06WokKhowMhbxxGsqOFWm0p-vV8KTr3trd17Lo9HgtgIjIg-N4PSisnjEBnja50j_KbRDUvbJW0VYaw/s320/w61ca-tw-login2.JPG" /></a></td> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs5VOte1NW4wd708S4JtYoaI-xsF7IwLS5IgZjQUhRYFSd4ryRc9gq5Ib0hhVd4ka00XEmFLjPFNNLFIHT-9pl2lZiJq6mVoy6_sE9Nu7xXQeJga-AfraN2185k_7T1aCIlK9iL_pJyZw/s1600/w61ca-tw-done.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs5VOte1NW4wd708S4JtYoaI-xsF7IwLS5IgZjQUhRYFSd4ryRc9gq5Ib0hhVd4ka00XEmFLjPFNNLFIHT-9pl2lZiJq6mVoy6_sE9Nu7xXQeJga-AfraN2185k_7T1aCIlK9iL_pJyZw/s320/w61ca-tw-done.JPG" /></a></td> </tr>
<tr> <td>特に問題なし!</td> <td>自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK!</td> </tr>
</tbody></table><br />
<h4>docomo Cookie対応機種(N-07A)でTwitterのOAuthをやってみた</h4><table><tbody>
<tr><th>ログイン画面</th> <th>承認画面</th> </tr>
<tr> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSXMGAzsTYZh9p4Gv_ltzzbG2wZITPgaAUvkWakwzI5D28fSQz1qDocrcj4t1vAfm20eAVfbR2rMm5dGQiZe3Kn9a6E81n9oVVbTYxWC27Kj8iXaqhHh1p5AMS7f0v6lIEBYVlTuseJPQ/s1600/n07a-tw-login2.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSXMGAzsTYZh9p4Gv_ltzzbG2wZITPgaAUvkWakwzI5D28fSQz1qDocrcj4t1vAfm20eAVfbR2rMm5dGQiZe3Kn9a6E81n9oVVbTYxWC27Kj8iXaqhHh1p5AMS7f0v6lIEBYVlTuseJPQ/s320/n07a-tw-login2.JPG" /></a></td> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1efqvF2mSsc5AzTg-tFM10S4XnL9HfefqyP0ToXvFyQmmh2vyx5Z-RT_UWTKtOKgTAcDsmUqwxtXuwBuaQDngrscN5fuU48P4zPOf5yKy-n8DXLzBaeUhgUeHQ20DJzZ96vc3xJnxoow/s1600/n07a-tw-done.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1efqvF2mSsc5AzTg-tFM10S4XnL9HfefqyP0ToXvFyQmmh2vyx5Z-RT_UWTKtOKgTAcDsmUqwxtXuwBuaQDngrscN5fuU48P4zPOf5yKy-n8DXLzBaeUhgUeHQ20DJzZ96vc3xJnxoow/s320/n07a-tw-done.JPG" /></a></td> </tr>
<tr> <td>文字化けがひどいが、ID・パスワード欄っぽいところに入れて1つ目のボタン(ログイン)を押せば承認画面に飛ぶ。</td> <td>自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK!</td> </tr>
</tbody></table><br />
<h4>docomo Cookie非対応機種(SH704i)でTwitterのOAuthをやってみた</h4><table><tbody>
<tr><th>ログイン画面</th> <th>承認画面</th> </tr>
<tr> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAhNalet3oNpGuP4Rx8gOIphSNtADtPBAV3cj1NtG0Jto0QsNrLMH3wunBqJI9_nuqvK-7aSSVEweFaFUOkMVilgtZL18RLm0Awv4dwbHFsGToo30cYxNOua7BT0WIZEYKfxJXWicpC6M/s1600/sh704i-tw-login.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAhNalet3oNpGuP4Rx8gOIphSNtADtPBAV3cj1NtG0Jto0QsNrLMH3wunBqJI9_nuqvK-7aSSVEweFaFUOkMVilgtZL18RLm0Awv4dwbHFsGToo30cYxNOua7BT0WIZEYKfxJXWicpC6M/s320/sh704i-tw-login.JPG" /></a></td> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz1niYQgIoePsBvC7zzcux0-6LOJd14SQpqZlsmNGKC5lKvav3kd3oIOYPcH3ELP37tj8OVcuLEOSbvBuFziJ9kuMk-1Kj_Wn3HHsMS4tKflHhX_dOQTtsbz0Av2dNIZylHlhiCjaj26A/s1600/sh704i-tw-done.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhz1niYQgIoePsBvC7zzcux0-6LOJd14SQpqZlsmNGKC5lKvav3kd3oIOYPcH3ELP37tj8OVcuLEOSbvBuFziJ9kuMk-1Kj_Wn3HHsMS4tKflHhX_dOQTtsbz0Av2dNIZylHlhiCjaj26A/s320/sh704i-tw-done.JPG" /></a></td> </tr>
<tr> <td>両方とも送信ボタン…。1つ目を押せば承認画面に飛ぶ。</td> <td>文字化けがひどいが、リンクを手動でクリックすれば動作はOK!</td> </tr>
</tbody></table><br />
<h4>まとめ</h4> docomoだと文字化けがひどいが、OAuthの機能的には動作する。ただし最後のリダイレクトを手動でクリックしないといけないのでユーザにこれを強いるのは難しい。<br />
※実際、OAuth開始前の画面に注意書きを入れたにもかかわらず、最後のリダイレクトのクリックが分からず、トークンが取得できていないユーザ多数いた<br />
PIN入力方式にすればよいが、PC・スマートフォンでも同じコンシューマキーを使いたいので、PC・スマートフォンでのユーザビリティが落ちるので却下。PCでOAuth認証だけ先にさせるのも携帯のみのユーザのことを考えると却下。<br />
→結果、xAuthを使うことにした。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-82267529262869624992010-05-15T22:19:00.005+09:002010-05-15T23:25:26.331+09:00[OAuth] 携帯でfoursquareのOAuthを無理やりやってみた<a href="http://www.mb4sq.jp/">モバイルフォースクエア</a>の開発初期に、OAuthでfoursquareの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。<br />
<h4>AU(W61CA)でfoursquareのOAuthをやってみた</h4><table><tbody>
<tr> <th>ログイン画面</th> <th>承認画面</th> </tr>
<tr> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikGqb2_TQM4rls9xZGvVoGCdytCBZxRo1KSxizmIoStVNsblnNfDSZhTwRhgnqW6nET90VydHusEPbqliaPYd6x7hDwjWLo3vZFJ2JwxQIlD5nmPyG1znlezI5INxA9Yn1GXkEE_W0J6Q/s1600/w61ca-4sq-login.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikGqb2_TQM4rls9xZGvVoGCdytCBZxRo1KSxizmIoStVNsblnNfDSZhTwRhgnqW6nET90VydHusEPbqliaPYd6x7hDwjWLo3vZFJ2JwxQIlD5nmPyG1znlezI5INxA9Yn1GXkEE_W0J6Q/s320/w61ca-4sq-login.JPG" /></a></td><td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVFL-NNTuqqlr8MCbygepV0jjEPUelrHod84eSTI5mhfMdtnXFaTkttWYLp__JnUp5pk6IZHWy0nHlBwncxmyUx_EDsKZFgnA96DFRl_EhD4QCrtoKId2AOrIq4jFUrKQiphZHouPxrUY/s1600/w61ca-4sq-allow.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVFL-NNTuqqlr8MCbygepV0jjEPUelrHod84eSTI5mhfMdtnXFaTkttWYLp__JnUp5pk6IZHWy0nHlBwncxmyUx_EDsKZFgnA96DFRl_EhD4QCrtoKId2AOrIq4jFUrKQiphZHouPxrUY/s320/w61ca-4sq-allow.JPG" /></a></td> </tr>
<tr> <td>日本語文字化け…</td> <td>文字化け&Denyが押せないが、動作はOK!</td> </tr>
</tbody></table><br />
<h4>docomo Cookie対応機種(N-07A)でfoursquareのOAuthをやってみた</h4><table><tr><th>ログイン画面</th> <th>承認画面</th> </tr>
<tr><td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd8vA90fYfHh49dtwUSsfzAZztqW5KQVCcftq7lxr6x3fi6bicbcdc3NgTJZnnCXNm2SgkYMudsILJRoIhQGdZ5hxyNyk4KqZp7Ofr4mi3s8jhfCkV1pwgbM0jkr5BUOdI4K4b2b7VtEU/s1600/n07a-4sq-login.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd8vA90fYfHh49dtwUSsfzAZztqW5KQVCcftq7lxr6x3fi6bicbcdc3NgTJZnnCXNm2SgkYMudsILJRoIhQGdZ5hxyNyk4KqZp7Ofr4mi3s8jhfCkV1pwgbM0jkr5BUOdI4K4b2b7VtEU/s320/n07a-4sq-login.JPG" /></a><br />
</td> <td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfwQS7xMTF7RnCQy9GwX6xSdyujFHoy75oAwnJL3exfbSsrWDR3NG0HhwRYs9auUkQPKFmA2fbiYxLzJdLDxEm3_h2tENDwPthd30jKanocmYEZoGKeJJ2aR8CR48OlwYNvQaFMcQVZJY/s1600/n07a-4sq-allow.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfwQS7xMTF7RnCQy9GwX6xSdyujFHoy75oAwnJL3exfbSsrWDR3NG0HhwRYs9auUkQPKFmA2fbiYxLzJdLDxEm3_h2tENDwPthd30jKanocmYEZoGKeJJ2aR8CR48OlwYNvQaFMcQVZJY/s320/n07a-4sq-allow.JPG" /></a><br />
</td> </tr>
<tr> <td>デザインは崩れるが、動作はOK!</td> <td>デザインは崩れるが、動作はOK!</td> </tr>
</table><br />
<h4>docomo Cookie非対応機種(SH704i)でfoursquareのOAuthをやってみた</h4>ログイン画面→表示が崩れ、ログインボタンを押しても同じ画面が繰り返し表示されるだけ…。<br />
※当時はログインできていた記憶が…、サーバ側の実装が変わったかもしれないです<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2V1k4r2A0_c8XSL0DiTwD0asAsF-ZLxh5BCHzgpytfcTJsDAnCaqom7ps_HowmDPquXu_-vvBR6P0KnOY9VtAwcpfQHfdrpSf6XLHf-wlatOEiFqewn9JaplyA1NQBsy9dXwRdi5lfsE/s1600/sh704i-4sq-login.JPG"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2V1k4r2A0_c8XSL0DiTwD0asAsF-ZLxh5BCHzgpytfcTJsDAnCaqom7ps_HowmDPquXu_-vvBR6P0KnOY9VtAwcpfQHfdrpSf6XLHf-wlatOEiFqewn9JaplyA1NQBsy9dXwRdi5lfsE/s320/sh704i-4sq-login.JPG" /></a><br />
<br />
<h4>まとめ</h4>Cookie対応機なら文字化けしたりデザインは崩れるが、OAuthの処理自体は正常。文字化けがひどい状態ではサービス提供できないので、foursquareの中の人に日本の携帯ブラウザ事情を説明し、<a href="http://groups.google.com/group/foursquare-api/web/oauth">Auth Exchange</a>採用。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-54653993276052156152010-04-27T13:16:00.004+09:002010-06-26T21:19:18.326+09:00[Scala][GAE] dispatchのOAuthをGAEで使うdispatchが内部でcommons-httpclientを使っていて、GAEでの禁止クラスを使っているのでそのままだとエラーになった。ので無理やりimplicit defでURLFetchのHTTPRequestに変換することで回避してます。こんな感じ。<br />
<br />
<pre name="code" class="scala">import dispatch._
import com.google.appengine.api.urlfetch._
import java.net.URL
object DispatchHelper {
/**
* convert dispatch.Request -> String
*/
implicit def r2s(r: Request) = {
r.host.get + r.req.getRequestLine.getUri
}
/**
* convert dispatch.Request -> com.google.appengine.api.urlfetch.HTTPRequest
*/
implicit def dispatch2gae(r: Request): HTTPRequest = {
val isPost = r.req.getMethod == "POST"
val g = new HTTPRequest(new URL(r), if (isPost) HTTPMethod.POST else HTTPMethod.GET, FetchOptions.Builder.withDeadline(10))
r.req.getAllHeaders.foreach(h => {g.addHeader(h); println(h)})
r.req match {
case p: org.apache.http.client.methods.HttpPost =>
val payload = new java.io.ByteArrayOutputStream
p.getEntity.writeTo(payload)
g.setPayload(payload.toByteArray)
println(p.getEntity.getContentType)
case _ =>
}
g
}
}
</pre><br />
こんな感じでimplicit defを宣言してimport DispatchHelper._すれば、dispatchでリクエストを作って、URLFetchService.fetch()の引数にそのまま渡せばOK。<br />
<br />
<a href="http://pomu0325.blogspot.com/2010/02/scala-scalaoauth.html">dispatchの解説はこちら</a><br />
<br />
<h4>2010-06-26追記</h4><a href="http://pc12.2ch.net/test/read.cgi/tech/1275891974/199n-" target="_blank">2chで質問</a>受けてコードが一部足りなかったので追記しました。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-71200900010158669002010-03-25T20:04:00.010+09:002010-03-25T23:53:52.130+09:00[Scala] Elemのlabel(要素名)を書き換えるこんな感じか?<br />
<pre name="code" class="scala">def changeTagName[T >: Node](from: String, to: String)(in: T): T = in match {
case Elem(p, l, a, ns, child @ _*) =>
val label = if (l == from) to else l
Elem(p, label, a, ns, child.map(changeTagName(from, to)(_)): _*)
case x => x
}
</pre><br />
<h4>使用例</h4><pre name="code" class="scala">scala> val hoge2moge = changeTagName("hoge", "moge") _
scala> hoge2moge: (scala.xml.Node) => scala.xml.Node = <function>
scala> hoge2moge(<pre:hoge attr="value">parentText<child:hoge>childtext</child:hoge></pre:hoge>)
scala> res17: scala.xml.Node = <pre:moge attr="value">parentText<child:moge>childtext</child:moge></pre:moge>
</pre><br />
<h4>追記:さらに汎用化してみた</h4>高階関数化して、引数に名前を変更するための関数を渡せるようにした。<br />
<pre name="code" class="scala">def changeTagName[T >: Node](changeFunc: (String) => String)(in: T): T = in match {
case Elem(p, l, a, ns, child @ _*) =>
Elem(p, changeFunc(l), a, ns, child.map(changeTagName(changeFunc)(_)): _*)
case x => x
}
</pre><br />
<h4>使用例</h4><a href="http://kaitenn.blogspot.com/2010/03/scala-elemlabel.html">お題はこちら</a>。<br />
<pre name="code" class="scala">scala> val c = changeTagName(s => if (List("moge", "koge").contains(s)) "bege" else s) _
c: (scala.xml.Node) => scala.xml.Node = <function>
scala> c(x)
res6: scala.xml.Node =
<lage>
<hoge>
<bege></bege>
<bege></bege>
</hoge>
<hoge>
<bege></bege>
<bege></bege>
</hoge>
</lage>
</pre><br />
<h4>23:52 さらにちょっと修正</h4>flatMap使えば別にNodeSeq渡せることに気がついた。<br />
<pre name="code" class="scala">def changeTagName(changeFunc: (String) => String)(in: NodeSeq): NodeSeq = in match {
case Elem(p, l, a, ns, child @ _*) =>
Elem(p, changeFunc(l), a, ns, child.flatMap(changeTagName(changeFunc)(_)): _*)
case x => x
}
</pre>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-57763461513505110962010-03-22T00:50:00.007+09:002010-03-22T00:59:31.761+09:00[Lift][GAE] spin-up時間を測定してみる<a href="http://d.hatena.ne.jp/yuroyoro/20100321/1269128061" target="_blank">@yuroyoroさんがGAE上のScalaのスピンアップ時間を測定していた</a>ので、倣ってLiftのspin-up時間を測定してみた。前は数回適当に起動した時間を見ていて7-8秒って感じだったのを、もうちょっと正確に。<br />
<br />
測定したのはarchetype:blankで生成される、LiftテンプレートとSnippetを使用したHelloWorld。また、不要なjarを減らしたら早くなるのかも気になったのでいくつかパターンを用意してみた。<br />
<h4>結果</h4><table border="1" style="border-collapse: collapse;"><tbody>
<tr><th>app</th><th>ave</th><th>max</th><th>min</th></tr>
<tr><td>lift-blank *1</td><td>6829</td><td>7338</td><td>5938</td></tr>
<tr><td>nomapper *2</td><td>6681</td><td>7105</td><td>6035</td></tr>
<tr><td>nomapper/nojson *3</td><td>6531</td><td>7124</td><td>5705</td></tr>
<tr><td>min *4</td><td>6481</td><td>7240</td><td>5957</td></tr>
</tbody></table><ol><li>Lift2.0-M3のarchetype-blankをそのままGAEに載せたもの</li>
<li>GAE上ではあまり使わないと思われるlift-mapperのjarを取り除いたもの</li>
<li>↑からさらにlift-jsonのjarを取り除いたもの</li>
<li>↑からさらにHelloWorldには不要なjar(javax.mail, commons-codec)を全て取り除いたもの</li>
</ol><h4>考察</h4><ul><li>簡単なLiftテンプレート+Snippetだと、Lift単品では7秒弱。</li>
<li>maxとminが前後しているのは気になるが、平均で見ると不要なjarを取り除くと若干早くなるっぽい。<br />
※前測って7-8秒だったのは、Lift以外のライブラリのjarがあったのでさらにオーバーヘッドがかかったのか?<br />
</li>
</ul><h4>ほか気づいたこと</h4>Liftがリクエストの処理にかかった時間をログに出してくれるのだが、初回がやたら遅くて3秒ほどかかっているのでこれがspin-upの半分を占めている。2回目のリクエストからは数ms…。<br />
<pre>INFO - Service request (GET) / took 3056 Milliseconds</pre>次はこれを調べてみることにする。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-34520010895030596422010-03-17T13:57:00.008+09:002010-03-26T10:45:25.513+09:00[GAE] JavaでAppstatsを使ってみる3/26追記:<a href="http://code.google.com/intl/ja/appengine/docs/java/tools/appstats.html">やっと公式にドキュメントが追加されました</a><br />
<br />
GAE SDK 1.3.2より、JavaでもAppstatsが使えるようになりました。1.3.2-preの段階ではまだlabsのjarに入っているので、appengine-api-labs-1.3.2.jarをWEB-INF/libに含めて、web.xmlに↓こんな感じで設定するだけ。※この設定だとstats自身もフィルタ対象になってしまうので除外する方法調査中。<br />
<pre class="xml" name="code"><filter>
<filter-name>AppstatsFilter</filter-name>
<display-name>Appstats Filter</display-name>
<description>Appstats Filter</description>
<filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AppstatsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>AppstatsServlet</servlet-name>
<servlet-class>
com.google.appengine.tools.appstats.AppstatsServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AppstatsServlet</servlet-name>
<url-pattern>/stats/*</url-pattern>
</servlet-mapping>
</pre><br />
ブラウザで/stats/でを開くと、↓こんな統計が見れます。<br />
※/stats て最後のスラッシュを抜かすとなぜかリダイレクトループしてしまいます…<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf0z5sRvMz5EbgnUGGpduccfVf6GFhSNg3Qcl8wzaPtv6NiFmnS8RBbQmPQP2O5hMbd4Cxqts1Mz49TLr32-Tr88satAvr0IH_JcQt-2vlpL5ZmRivELFfM9XeX7iZzGXvhvq1PW-l0Dk/s1600-h/appstats.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf0z5sRvMz5EbgnUGGpduccfVf6GFhSNg3Qcl8wzaPtv6NiFmnS8RBbQmPQP2O5hMbd4Cxqts1Mz49TLr32-Tr88satAvr0IH_JcQt-2vlpL5ZmRivELFfM9XeX7iZzGXvhvq1PW-l0Dk/s640/appstats.jpg" width="640" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf0z5sRvMz5EbgnUGGpduccfVf6GFhSNg3Qcl8wzaPtv6NiFmnS8RBbQmPQP2O5hMbd4Cxqts1Mz49TLr32-Tr88satAvr0IH_JcQt-2vlpL5ZmRivELFfM9XeX7iZzGXvhvq1PW-l0Dk/s1600-h/appstats.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><br />
</a><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJH2tXRhYL-WWeAhxNNZZy89VLzAm8a2mC0lCJp4MBnJ35xSvOwoeow1GMMiO3P5Ep6bcXBa02HYXutQWDn1aYHzdQC4LUr5clth76HlppXZBEjyjsDNfs0OxHfpNeAbTeGWvP1EvN31M/s1600-h/appstats2.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJH2tXRhYL-WWeAhxNNZZy89VLzAm8a2mC0lCJp4MBnJ35xSvOwoeow1GMMiO3P5Ep6bcXBa02HYXutQWDn1aYHzdQC4LUr5clth76HlppXZBEjyjsDNfs0OxHfpNeAbTeGWvP1EvN31M/s640/appstats2.jpg" width="640" /></a></div>※IE8だと表示が崩れてました。<br />
<br />
これだけだとURLが分かれば誰でも見えてしまうので、管理者のみアクセスできるような設定もした方がよいでしょう。<br />
<pre name="code" class="xml"><security-constraint>
<web-resource-collection>
<url-pattern>/stats/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
</pre>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0tag:blogger.com,1999:blog-7854445846272488112.post-89569046719613918542010-03-17T03:29:00.004+09:002010-03-18T02:55:22.020+09:00[Lift][GAE] DataStoreに溜まったsessionをcronで削除する↓こんなobjectを作っておき、cronを設定した。だいたい550件程度削除すると30秒経過のタイムアウトでエラーになるけど別に気にしない。<br />
<pre name="code" class="scala">import net.liftweb.http._
import net.liftweb.common._
import com.google.appengine.api.datastore._
object SessionCleaner {
// Iteratorのimplicit conversionは定義されていないようなので自前で定義。
implicit def j2s[A](j: java.util.Iterator[A]) =
new scala.collection.jcl.MutableIterator.Wrapper[A](j)
private lazy val DSS = DatastoreServiceFactory.getDatastoreService
def execute(): Box[LiftResponse] = {
var count = 0
try {
val q = new Query("_ah_SESSION")
q.addFilter("_expires", Query.FilterOperator.LESS_THAN_OR_EQUAL, System.currentTimeMillis)
DSS.prepare(q).asIterator.foreach(e => {DSS.delete(e.getKey); count = count + 1})
} finally {
println(count + " sessions deleted.")
}
Full(OkResponse())
}
}
</pre><br />
Boot.scalaのdef boot内にはこんな感じ。<br />
<pre name="code" class="scala">LiftRules.statelessDispatchTable.append {
case Req("cron" :: "sessionCleaner" :: Nil, _, _) => () => SessionCleaner.execute()
}
</pre><h4>ソース全体はgithubに置きました</h4><ul><li><a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/scala/net/pomu/lift_sandbox/lib/SessionCleaner.scala">SessionCleaner.scala</a></li>
<li><a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/scala/bootstrap/liftweb/Boot.scala">Boot.scala</a></li>
<li><a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/webapp/WEB-INF/cron.xml">cron.xml</a></li>
</ul>pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com1tag:blogger.com,1999:blog-7854445846272488112.post-51447675983786371512010-03-16T02:06:00.007+09:002010-03-16T02:13:47.773+09:00[Lift] 無駄に新規セッションを生成させずに条件によってリダイレクトする方法リクエストヘッダの条件によってリダイレクトを行うには、snippet内でS.redirectToを使ってリダイレクトするのが一番簡単な方法ですが、Liftでsnippetを使うと必ずセッションを生成してくれます。検索エンジンのクローラbotとかからアクセスがある度に無駄にセッションが生成されてしまってサーバリソースを無駄に使っていたので、こんな感じで不要な場合はセッションが生成される前にリダイレクトさせてみました。<br />
<pre name="code" class="scala">// Boot.scala の def boot内
LiftRules.statelessDispatchTable.prepend { // *1
case MyReq("venue" :: vid :: _, r) if r.header("User-Agent") == Full("何か") => // *2
() => Full(RedirectResponse("http://foursquare.com/venue/" + vid))
}
object MyReq { // *3
def unapply(in: Req): Option[(List[String], HTTPRequest)] =
Some((in.path.partPath, in.request))
}
</pre><ol><li>LiftRules.<strong>statelessDispatchTable</strong>にPartialFunction[Req, () => Box[LiftResponse]]を追加しておくと、セッション生成前に条件にマッチした場合は戻り値のLiftResponseをクライアントに返してくれます。</li>
<li>通常は、Req(パス, 拡張子, リクエストタイプ)でパターンマッチさせますが、ここではリクエストの内容によって判断したいため、*3で独自の<strong>抽出子(extractor)</strong>を定義し、抽出したHTTPRequestの中身(上記例ではリクエストヘッダ)を<strong>パターンガード</strong>条件として使用しています。</li>
<li>unapplyメソッドを持つobjectが<strong>抽出子</strong>です。参照:<a href="http://www.amazon.co.jp/dp/4844327453?tag=withp-22&camp=1027&creative=7407&linkCode=as4&creativeASIN=4844327453&adid=0D58W4YYBD2CB7QR1CQX&">コップ本</a>第24章(p.440)</li>
</ol>抽出子とパターンガードを使うとパターンマッチが益々便利です。pomu0325http://www.blogger.com/profile/07153996876142338944noreply@blogger.com0