気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
さて、今回から本格的な設計に入っていきたいと思います。
まずは、システムが扱う情報を整理し、データストアの構造を決定する作業から始めたいと
思います。

「Google App Engine」の場合、データを保存する先がリレーショナルデータベースでは
ないので、最終的には「Google App Engine」の作法に則った設計をしなければ
いけないのですが、まずはそれを意識せず、リレーショナルデータベースを想定した
エンティティ分析を行ってみたいと思います。

エンティティ分析
このシステムで中心的なエンティティとなるのは、言うまでもなく「物品」になります。
今のところ、物品の属性は、以下の9つと考えています。

[物品]
   種類
   置き場所
   物品名
   購入価格
   購入した日
   数量
   単位
   賞味期限
   その他特記事項

(前回の記事から「単位」と「種類」を追加しました。)

管理する物品によって、属性はまちまち(電気製品だと、メーカーなどが欲しいし、食品だと、産地とかが欲しい)
なのですが、扱う範囲を「家庭内の物品」と広くしているので、その一つ一つを取り入れると大変なことになって
しまいます。

ですので、物品の種類毎の特殊な情報は、「その他特記事項」に入力してもらうように考えています。
ただ、「賞味期限」だけは、使用頻度が高いと考えましたので、属性に入れました。

さて、次に上記を正規化して他のエンティティを抽出する作業を行いました。
属性「種類」と「置き場所」は他のエンティティに分割できるので、
正規化して以下ようにしました。また、同時にID属性を追加しました。

[物品]
   物品CD
   種別CD
   管理場所CD
   物品名
   購入価格
   購入した日
   数量
   単位
   賞味期限
   その他特記事項

[物品種別]
   種別CD
   種別名
   備考

[管理場所]
   管理場所CD
   管理場所名
   備考

今回アプリケーションは不特定多数のユーザに使って頂くWebサービスとして提供するので、
物品の情報が関係ないユーザに見えてしまうことがあってはいけません。

そのため、「台帳」というエンティティを追加し、他のエンティティはそれに属する形にします。
そして、台帳を利用できるユーザを限定するための「台帳ユーザ管理」エンティティを追加します。
システムのユーザに該当する「ユーザ」エンティティも追加します。
クラス図にすると以下のようになります。



現時点での最終的なエンティティ構成は以下のようになりました。

[台帳]
   台帳CD
   台帳名
   作成日時
   作成ユーザ

[台帳ユーザ管理]
   台帳CD
   ユーザ ※ユーザに関してはGAEで管理されるのでエンティティ定義はしません。

[物品]
   台帳CD
   物品CD
   種別CD
   管理場所CD
   物品名
   購入価格
   購入した日
   数量
   単位
   賞味期限
   その他特記事項
   登録日時
   登録ユーザ
   更新日時
   更新ユーザ

[物品種別]
   台帳CD
   種別CD
   種別名
   備考

[管理場所]
   台帳CD
   管理場所CD
   管理場所名
   備考

作成日時など管理用の属性も追加しました。

物品種別は全てのユーザ間で共有しても良い気がしますが、
固有の種別を追加したいニーズは必ず出てくると思いましたので、
台帳に属するようにしました。

基本的な種別に関しては台帳作成時に初期投入されるように工夫した方が良さそうです。

次回は、これをGAEのデータストアの規則に則って、データクラスの定義をしてみたいと思います。
スポンサーサイト
さて、連載2回目の今回は、連載で題材にするシステムについて簡単にご紹介したいと思います。
開発するシステムは家庭内の物品を管理するためのシステムです。

連載の大きな目的の1つは、GAEの開発に関する情報をお伝えすることですが、そのためには
題材にするシステムの仕様が出来るだけ分かり易い方が良いと思いました。
その意味で家庭内の情報を管理するシステムは企業情報システムよりもなじみ易いと考えました。

また、今回の連載はシステムを開発して終わりというのではなく、開発後、出来ればみなさまに
使っていただいて、GAEを運用するところも実証してみたいと思っています。

そのため、有る程度、実用性があるシステムを開発したいと思っています。
そういった理由で、家庭内の物品管理システムを題材に選びました。

少し、考えてみると家庭内にはたくさんの物品が存在します。
ちょっとあげてみるだけで、服、食品、電気製品、化粧品、洗面具などかなりの数になると思います。

私の家庭においては、これらの管理はほとんどかみさんの頭の中で行われています。ですので、例えばかみさんがいない時に
歯磨き粉が無いということになると、歯磨き粉無しで歯を磨くことになってしまいます。。
そんな時に新しい歯磨き粉がどこにあるかすぐに参照できるシステムが有ると便利だなぁと思っていました。

また、たまにかみさん孝行をしようと料理を作ることがあるのですが、冷蔵庫の在庫状態を把握できていないので何の食材を買えばいいか
分からず結局、かみさんと一緒に買い物をすることになります。

このような問題は結構、どこのご家庭でもあると思います。物品の情報は、家庭内においては家計とならんで量が多い情報ですので、
システム化による効用は有る程度と見込めると期待しています。

機能の紹介
本格的な設計は次回以降の記事で書かせてもらうとして、まず、簡単な機能の紹介をさせていただきます。
現時点で考えている物品管理システムの機能は以下のようなものです。

・家庭内の物品の管理(システムへの物品登録、削除、属性変更)
管理する物品の属性は「おき場所」、「物品名」、「購入価格」、「購入した日」、「数量」、「賞味期限」、「その他特記事項」

・物品の検索
物品の属性による検索

・お買い物リストメール送信
検索機能と連携し、品数が少なくなった物品を選択して、購入リストとしてメール送信する。

アーキテクチャについて
サーバ環境は言うまでもなく、「Google App Engine」です。開発言語としてJavaを採用したいと思います。
クライアントサイドは、「Silverlight」、もしくは「HTML + JavaScript」のどちらかで。
もうちょっと検討します。

次回は、設計に入ります。

本日から、新しい日記連載を始めます。お題は、「Google App Engine」です。

今年に入って平成大不況の波は、ありとあらゆる業種に押し寄せています。
そんな中、ちまたでは、百貨店などの買取セールや、マクドナルドのキャッシュバックなど、
いろいろな企業努力が試みられています。

私たち、IT業界に携わっているものにとっても何か起爆剤が出てきて欲しいと切に願っていますが、
筆者は「Google App Engine」はその1つとなれるのではと考えています。

「Google App Engine」の大きな特徴は何といっても無料で使用できることです。無料で使用できる範囲には制限が
あるのですが、月間500万ページビューが制限の基準となっているようですので、その上で実現できるシステムの幅は
結構広いと思っています。

「Google App Engine」は「B to C」のインフラとしても、もちろん魅力的なのですが、筆者は中小規模の業務システムの
インフラとして注目をしています。

一般的に、業務システムの効果は、システムが取り扱う情報の量と比例して、高くなると考えられています。
しかし、情報量と費用の関係は、必ずしも直線的な比例関係になっていないと考えています。

Excelとファイルサーバを使って何とか情報を管理しているIT利用の状態から、
何らかのアプリケーションサーバを設置して、情報を共有するレベルに移行すると、扱う情報量が飛躍的に
増えていない状況においても、費用だけは格段に上がってしまいます。



システムを構築する費用を別にしても、これまでに無かったアプリケーションサーバの運用保守にかかる費用が
新たに加わるためです。

良い例えかどうか分かりませんが、松竹梅の「竹」がない状態になってしまっているのです。

筆者は「Google App Engine」がこの竹の部分を補うインフラになると考えているのです。
繰り返しになりますが、「Google App Engine」には以下のような特徴があります。

・無料で利用できる
・インストールが簡単
(Eclipseのボタンをクリックするだけです。保守をベンダーがやる場合も
ベンダーの自社内から簡単に更新が可能。)
・Googleのインフラで稼動するため、サーバ管理の必要がない

これらの特徴は全て、システムにかかる費用に反映されるものです。
特に運用管理にかかる費用は、ほぼゼロになると思います。

この結果、百貨店のバーゲンセールのように、業務システムの大安売りが出来る時代が来たのではと筆者は期待しています。
そして、ひいては、中小企業のIT化を促進し、企業の競争力がまし、再び景気が盛り上がるみたいな展開に
なるとすばらしいと思います。

そこで、今回、「Google App Engine」を利用したソフトを開発し、それを実際に運用してみると
ところを日記にし、「Google App Engine」の利用を検討している方々のお役に立てればと思っております。

記事はあくまでも日記風にしたいので、失敗したことや、試行錯誤するところも含めて、書いていきたい
と思います。よろしくお願いします。

本日のTipsでは、先日、公開させて頂いた「Crete For Web」のコードを使ってDataGridの基本的な使用方法を紹介していきます。

DataGridにデータを表示する
「Crete For Web」ではホームページのキーワード解析を行った結果をDataGridに表示しています。
DataGridに表示している項目は[単語]、[単語の出現回数]、[単語の出現率]、[単語の出現するタグ]の4つ。



まずはXAMLにDataGridのタグを記述します。

[Page.xaml]
<UserControl x:Class="CreteWeb.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    Width="700">

   //省略

   <data:DataGrid x:Name="_wordList" Height="250" Width="580" IsReadOnly="True"
      VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" ></data:DataGrid>
</UserControl>

「System.Windows.Controls.Data.Dll」に参照を追加し、名前空間の記述を追加します。

そして、コードから参照できるように「x:Name」属性を設定しています。
また、スクロール可能なように、VerticalScrollBarVisibility属性とHorizontalScrollBarVisibility属性をAutoに設定しています。

続いて、コードを見ていきます。

まず、解析結果を管理するためのエンティティクラスを作成します。

    public class Keyword
    {
        //単語
        public String Word { get; set; }
        //単語の出現回数
        public String Count { get; set; }
        //単語の出現率
        public String Ratio { get; set; }
        //単語の出現するタグ
        public String Tags { get; set; }
    }

次にDataGridに設定する部分です。

[Page.xaml.cs]

  //複数のキーワードを管理するコレクション
  List keywords = new List();

  //形態素解析のAPIから取得したXMLのノードをループする
  foreach (var node in query)
  {
     Keyword keyword = new Keyword();

     //keywordの各プロパティに値を設定するコード(省略)

     keywords.Add(keyword);
  }

  //DataGridにKeywordのコレクションを設定する
  _wordList.ItemsSource = keywords;


ここまで、とりあえずDataGridにデータを表示することが出来ます。
DataGridのItemsSourceに指定できるオブジェクトは「foreach」可能なオブジェクトです。

列名を変更する
ここまでのコードでは、DataGridの列名には、Keywordクラスのプロパティ名がそのまま
表示されています。これを分かりやすい日本語に変更します。

XAMLのDataGridを配置している部分を以下のように変えます。

[Page.xaml]
<data:DataGrid x:Name="_wordList" Height="250" Width="580" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
          IsReadOnly="True" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="キーワード" Binding="{Binding Word}" />
        <data:DataGridTextColumn Header="出現回数" Binding="{Binding Count}" />
        <data:DataGridTextColumn Header="割合(%)" Binding="{Binding Ratio}" />
        <data:DataGridTextColumn Header="タグ" Binding="{Binding Tags}" />
    </data:DataGrid.Columns>
</data:DataGrid>

まず、DataGridのAutoGenerateColumns属性をFalseに設定します。これをやらないと同じ列が重複して出来てしまいます。
DataGridTextColumnのBinding属性には、それぞれの列に対応するKeywordクラスのプロパティ名を指定しています。
その他、Width属性を指定すると列の幅を設定することも出来ます。

選択行のオブジェクトを取得する
「Crete For Web」ではキーワードの一覧表(上記DataGrid)から項目を選択し、関連ワードボタンをクリックすると
選択された単語と一緒によく検索される関連語の一覧を取得することが出来ます。

この際、現在DataGridで選択されている行のKeywordクラスのインスタンスを、以下のようにして取得しています。

[Page.xaml.cs]
   Keyword keyword = _wordList.SelectedItems[0] as Keyword;

行の色を変更する
関連語一覧では、解析対象のページに含まれているキーワードの組み合わせについては、
青色で、その他の場合は、黒色で表示しています。

この処理は、DataGridのLoadingRowイベントを利用して行っています。

[Page.xaml.cs]
  public Page()
  {
      InitializeComponent();

       //イベントハンドラを追加
      _relatedList.LoadingRow += new EventHandler(_relatedList_LoadingRow);
  }

  private void _relatedList_LoadingRow(object sender, DataGridRowEventArgs e)
  {
      RelatedWord relatedWord = e.Row.DataContext as RelatedWord;

       //判定処理(省略)

      if (//ふくまれている)
      {
          e.Row.Foreground = new SolidColorBrush(Colors.Blue);
      }
      else
      {
          e.Row.Foreground = new SolidColorBrush(Colors.Black);
      }
   }

LoadingRowイベントはDataGridRow がインスタンス化され、画面に表示される前に発生します。
両方のケースについてしっかりと色指定を行わないと、条件に合わない行まで青色で表示されたりするので、
ご注意ください。

余談ですが、DataGridはItemsSourceにデータが設定された段階で全ての行をインスタンス化するのではなく、
行が必要になった時点で、インスタンス化を行います。この仕様のおかげで画面表示がすばやく行われます。
この度、SEO分析ツール「Crete」のWeb版 「Crete For Web」をリリースします。

「Crete For Web」の機能は以下です。

・ホームページに含まれる[単語]、[単語の出現回数]、[単語の出現率]、[単語の出現するタグ]の解析
・ホームページに含まれる[単語]と共によく検索される関連語の取得

■動作環境
本アプリケーションはMicrosoftのSilverlight2を使用しています。ご使用のPCにSilverlight2がインストールされていない場合、
インストールを促すアイコンが表示されますので、画面の指示に従ってインストールを行ってください。

■使用方法

1.ページのソースを貼り付ける
「ソース:」のテキストボックスに解析したいページのHTMLソースを貼り付けます。
HTMLソースは、ブラウザで解析したいページに移動し、右クリックから「ソースの表示(IEの場合、ソースの表示、FireFoxの場合、ページのソースを表示)」を
クリックします。
そして、メモ帳などに表示されたテキストを全て選択し、「ソース:」以下のテキストボックスに貼り付けます。

※使用しているYahoo APIの制限で1リクエストの最大サイズが100KB、文字数にして約33,000文字に制限されていますので、
ブログのトップページなど文字数が多いページはエラーになる可能性があります。

2.ページの解析
「解析」ボタンをクリックすると、解析ボタン下の表に解析結果が表示されます。
解析には少々時間がかかりますので、2度ボタンを押したりせず、結果が表示されるのをお待ちください。

3.関連ワードの取得
解析結果の表で単語を選択し、「関連ワード」ボタンをクリックすると、
その単語と一緒によく検索される関連語がボタン下の表に表示されます。

青色で表示される関連語は現在ページに含まれている単語の組み合わせになります。

■SEOにおけるキーワード分析について
キーワードは検索エンジンの順位を決定する要因の1つですので、それだけですぐに効果現れると
いうものではありません。
しかしながら、自分のサイトが狙っているキーワードで上位にランクインしているサイトのキーワード構成を参考にすることで
上位にランクインする可能性が高くなると思います。

■Windows版Creteについて
Windows版のCreteは、Web版の機能に加えて、URLからのソーステキストの取得、キーワード一覧のCSVファイル出力機能を
備えています。Creteは以下のURLからダウンロードできます。

Creteのダウンロード


■Crete For Web




Webサービス by Yahoo! JAPAN


Google Data APIなどユーザ認証が必要なWeb APIを利用してマッシュアップサイトを作成する場合、
ユーザの認証情報をどのように扱うのかはとても気がかりな問題になります。

Google Data APIの場合、Googleアカウントを扱うことになりますが、GoogleアカウントはGmailなどいろいろな
種類のサービスを利用することが出来るため、その認証情報(ユーザID、パスワード)は非常に厳重に扱われるべき情報になります。

このようなケースにおいて、マッシュアップサイトの運営者が直接、認証情報を扱わずにその先のサービス(Google Data APIなど)
を利用できるようにする標準プロトコルとしてOAUTH(オース)というものが開発されました。

今回、このOAUTHプロトコルを使用してGoogle Calendar APIにアクセスし、カレンダー情報を取得するコードを書いてみました。
開発環境には前回に引き続き、Google App EngineのJava版を使用しました。

ドメインの登録
GoogleのOAUTHを使用するためには、サービスを利用するマッシュアップサイトのドメインをあらかじめ登録して
おく必要があります。

ドメインの登録

画面の指示に従って、ドメインを登録すると、最後のページに「OAuth Consumer Key」と「OAuth Consumer Secret」と
いう項目が表示されます。この2つの項目の値は認証処理を行う際に使用しますので、メモをしておいてください。

ライブラリのダウンロード
Google Data API(Google Calendar APIはGoogle Data APIの一部)はAPIを扱うためのクライアントライブラリを
提供しています。以下のURLから、Java版のクライアントライブラリをダウンロードできます。

Java版クライアントライブラリ

ダウンロードしてファイルを解凍して、以下のjarファイルをプロジェクトにインポートします。

gdata-base-1.0.jar
gdata-calendar-2.0.jar
gdata-calendar-meta-2.0.jar
gdata-client-1.0.jar
gdata-client-meta-1.0.jar
gdata-core-1.0.jar

認証処理
コードは認証処理を行うAuthServletとカレンダー取得を行うCalendarServletに分けました。

以下のコードはローカルの開発環境では正常に動作をしています。
しかし、Google App Engineにインストールすると、カレンダーの取得時に
「com.google.gdata.util.ServiceException: Invalid redirected-to URL - null」というエラーが
発生します。

Google App EngineやGoogle Data APIに関してはWeb上のリソースも少なく、
原因解決に苦戦しています。同じような現象に直面している方がいたら
情報をいただけるとうれしいです。

認証部分についてもこれが確実にあっていると確信できていないので、間違いがあれば指摘してください。

以下のURLにある図をみながら以下のソースを見ていただくと分かりやすいかもしれません。

Account Authentication API

public class AuthServlet extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse resp)
  throws IOException {
    
    String pathInfo = req.getPathInfo();
    
    if(pathInfo.equals("/login"))
    {
      login(req,resp);
    }
    else if(pathInfo.equals("/authsub"))
    {
      saveAccessToken(req,resp);
    }
  }

  private void login(HttpServletRequest req, HttpServletResponse resp)
  {
    try
    {
      //パラメータを設定する
      GoogleOAuthParameters params = new GoogleOAuthParameters();
      params.setOAuthConsumerKey(ドメイン登録時に取得したOAuth Consumer Key);
      params.setOAuthSignatureMethod("HMAC-SHA1");
      params.setScope("http://www.google.com/calendar/feeds/");
      params.setOAuthConsumerSecret(ドメイン登録時に取得したOAuth Consumer Secret);
      
      //Step1~2:リクエストトークンを取得する
      GoogleOAuthHelper helper = new GoogleOAuthHelper(new OAuthHmacSha1Signer());
      helper.getUnauthorizedRequestToken(params);
      
      //以下、Step3~6
      
      //TokenSecretを取得する
      String tokenSecret = params.getOAuthTokenSecret();
      
      //開発環境の場合
      if(req.getServerName().equals("localhost"))
      {
        params.setOAuthCallback(String.format("http://localhost:8080/auth/authsub?secret=%s",
            URLEncoder.encode(tokenSecret, "UTF-8")));
      }
      //App Engineの場合
      else
      {
        params.setOAuthCallback(String.format("http://riamediator.appspot.com/auth/authsub?secret=%s",
            URLEncoder.encode(tokenSecret, "UTF-8")));
      }      
    
      //認証用URLを作成する
      String authUrl = helper.createUserAuthorizationUrl(params);
      
      //認証ページにリダイレクトする
      resp.sendRedirect(authUrl);
    }
    catch(Exception ex)
    {
      resp.setContentType("text/html;charset=UTF-8");
      
      try
      {
        resp.getWriter().println(String.format("<html><body>%s</body></html>",ex.toString()));
      }
      catch(Exception e){}
    }  
  }

  //アクセス用トークンを取得する(Step7~8に該当)
 //ユーザが認証情報を入力後、リダイレクトされてコールされる
  private void saveAccessToken(HttpServletRequest req, HttpServletResponse resp)
  {
    try
    {    
      //パラメータを設定する
      GoogleOAuthParameters params = new GoogleOAuthParameters();
      params.setOAuthConsumerKey(ドメイン登録時に取得したOAuth Consumer Key);
      params.setOAuthSignatureMethod("HMAC-SHA1");
      params.setOAuthConsumerSecret(ドメイン登録時に取得したOAuth Consumer Secret);
      params.setOAuthTokenSecret(req.getParameter("secret"));
      
      //アクセス用トークンを取得する
      GoogleOAuthHelper helper = new GoogleOAuthHelper(new OAuthHmacSha1Signer());
      helper.getAccessToken(req.getQueryString(),params);
      
      //トークンをクッキーに設定する
      Cookie cookie = new Cookie("access_token",params.getOAuthToken());
      cookie.setPath("/");
      cookie.setMaxAge(60 * 60 * 24 * 14);  //有効期限を2週間に設定      
      resp.addCookie(cookie);
      
      cookie = new Cookie("token_secret",params.getOAuthTokenSecret());
      cookie.setPath("/");
      cookie.setMaxAge(60 * 60 * 24 * 14);  //有効期限を2週間に設定      
      resp.addCookie(cookie);      
      
      resp.getWriter().println("<html><head>");
      
      //開発環境の場合
      if(req.getServerName().equals("localhost"))
      {
        resp.getWriter().println("<meta HTTP-EQUIV='Refresh' CONTENT='0; URL=http://localhost:8080/cal/list'>");        
      }
      //App Engineの場合
      else
      {
        resp.getWriter().println("<meta HTTP-EQUIV='Refresh' CONTENT='0; URL=http://riamediator.appspot.com/cal/list'>");  
      }      
      

      resp.getWriter().println("</head><body></body></html>");
      
    }
    catch(Exception ex)
    {
      resp.setContentType("text/html;charset=UTF-8");
      
      try
      {
        resp.getWriter().println(String.format("<html><body>%s</body></html>",ex.toString()));
      }
      catch(Exception e){}
    }
  }

public class CalendarServlet extends HttpServlet {

  private static final Logger _log = Logger.getLogger(CalendarServlet.class.getName());
  
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
  throws IOException {
    
    try
    {
      resp.setContentType("text/html;charset=UTF-8");
      resp.getWriter().println("<html><body>");
      
      //クッキーからトークンを取得する
      Cookie[] cookies = req.getCookies();
      
      String accessToken = null;
      String tokenSecret = null;
      
      if(cookies != null)
      {
        for(Cookie cookie : cookies)
        {
          if(cookie.getName().equals("access_token"))
          {
            accessToken = cookie.getValue();
          }
          else if(cookie.getName().equals("token_secret"))
          {
            tokenSecret = cookie.getValue();
          }            
        }
      }
      
      GoogleOAuthParameters params = new GoogleOAuthParameters();
      params.setOAuthConsumerKey(ドメイン登録時に取得したOAuth Consumer Key);
      params.setOAuthConsumerSecret(ドメイン登録時に取得したOAuth Consumer Secret);
      params.setOAuthSignatureMethod("HMAC-SHA1");
      params.setOAuthToken(accessToken);
      params.setOAuthTokenSecret(tokenSecret);
      params.addCustomBaseParameter("oauth_version","1.0");
      
      CalendarService service = new CalendarService("riamediator-riamediator-1");
      service.setOAuthCredentials(params, new OAuthHmacSha1Signer());
      
      URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/allcalendars/full");
      CalendarFeed resultFeed = service.getFeed(feedUrl, CalendarFeed.class);
      
      for (int i = 0; i < resultFeed.getEntries().size(); i++) {
        CalendarEntry entry = resultFeed.getEntries().get(i);
        resp.getWriter().println(entry.getTitle().getPlainText() + "<br>");
      }
      
      resp.getWriter().println("</body></html>");
  
    }
    catch(Exception ex)
    {      
      try
      {
        resp.getWriter().println(String.format("<html><body>%s</body></html>",ex.toString()));
      }
      catch(Exception e){}
    }
  }

ブログの名前にC#としておきながら、最近、その他の内容が多くてすみません。
Google App Engineとクライアント側のC#を組み合わたソフトを作りたくて、Pythonで
あれこれやっていたところ、先週火曜日4月8日に、Google App EngineのJavaサポートが
発表されたため、つい飛びついてしまいました。

しばらく、Javaから遠ざかっていたので、開発環境を整えるところからスタートしました。

JDK1.6をインストールする
Google App EngineのJavaはJava6のJVMを採用しているので、開発する際にもJDKの1.6を使うことが推奨されます。
ただ、どうしても1.6が手に入らない場合は、1.5でも開発できるようです。

私は以下のURLからJDK1.6をインストールしました。

http://java.sun.com/javase/downloads/index.jsp

Eclipse3.4をインストールする
「Google App Engine SDK」とコマンドラインを使って開発することも出来るのですが、やはりEclipseを使った方が断然、便利です。
Eclipse3.3または3.4に対して、Google App EngineのPluginが提供されているので、そのどちらかをインストールします。
私は3.4をインストールしました。インストール作業は以下のURLのサイトを参考にさせていただきました。ありがとうございました!

「金子研究室」

Google Plugin for Eclipseをインストールする
次にGoogle App Engineの開発用プラグインをインストールします。ヘルプ→ソフトウェア更新で以下のURLからインストールします。

http://dl.google.com/eclipse/plugin/3.4

「Google Plugin for Eclipse3.4」と「Google App Engine Java SDK 1.2.0」をインストールすればOKです。
「Google Web Toolkit SDK 1.6.4」は任意です。

インストールする際、Eclipseは他のプラグインとの依存関係をチェックしながらインストールするため、とてもなが~い時間がかかってしまいます。
これを防ぐためには、Google App Engineのロケーションを除く、登録済みのロケーションのチェックを一旦外し、チェックが走らないようにします。
これをすると、すんなりインストールが終わります。



まだ日本語環境をインストール出来ていない状態の画像なので、分かりにくいかもしれませんが、上の画像の[ManageSites..]ボタンの位置にある
ボタンからロケーションの一覧を開けると思います。

プロジェクトを作成する
プラグインをインストールし、Eclipseを再起動すると、ファイルメニューの新規作成にGoogle App Engine用の
Webアプリケーションプロジェクトが表示されるはずですので、それを選んでプロジェクトを作成します。



プロジェクトのフォルダ構成は通常の「動的Webプロジェクト」とは少し異なっているのですが、
Javaに馴染んでいる方であれば、違和感はないと思います。

こんな感じです。

[プロジェクト名]
     |--[src] ・・・ソース
     |--[war]
        |--index.html
        |--[WEB-INF]
             |--[classes]
             |--[lib]
             |--appengine-web.xml  ・・・App Engine用の設定ファイル
             |--web.xml

YoutubeのFeedを取得するサーブレットを作成する
前回の記事でPythonで作ったYoutubeのFeedを取得するアプリをJavaで作ってみました。

@SuppressWarnings("serial")
public class RiaMediatorServlet extends HttpServlet {
  
  private static final Logger _log = Logger.getLogger(RiaMediatorServlet.class.getName());
  
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("text/xml;charset=UTF-8");

    BufferedReader reader = null;
    PrintWriter writer = null;
    
    try
    {
      writer = new PrintWriter(new OutputStreamWriter(resp.getOutputStream(),"UTF-8"));
      
      URL url = new URL("http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured?start-index=1");
        reader = new BufferedReader(new InputStreamReader(url.openStream(),"UTF-8"));
        
        String line;
        while( (line = reader.readLine()) != null)
        {
          writer.println(line);
        }        
    }
    catch(Exception ex)
    {
      _log.severe(ex.toString());
    }
    finally
    {
      try
      {
        if(reader != null)
        {
          reader.close();
        }
      }
      catch(Exception ex){}

      try
      {
        if(writer != null)
        {
          writer.close();
        }
      }
      catch(Exception ex){}
    }
  }
}

Feedの取得は、URLクラスを利用して行っています。URLクラスをはじめとするjava.netのクラスは、Google App Engine用の実装が
されているので、実際の処理はGoogle App Engineのインフラストラクチャを使って行われます。

また、Pythonと同じく、SandBox内で実行されるため、以下のような制限があります。
「接続できるポートはHTTP(80)とHTTPS(443)のみ」

Google App Engineへのデプロイ
Google App Engineへのデプロイもとても簡単です。まずは、Google App Engineに登録してアプリケーションIDを取得してください。
そして、「appengine-web.xml」の「application」欄に取得したアプリケーションIDを入力します。



そして、ツールバーのGoogle App Engineマークのアイコンをクリックし、表示されるダイアログにEmailとパスワードを入力して実行します。
これで完了です。



ここで以下のようなエラーが出る場合があります。

Creating staging directory
Scanning for jsp files.
Scanning files on local disk.
Initiating update.
Unable to upload:
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=riamediator&version=1&
400 Bad Request
Invalid runtime specified.

	at com.google.appengine.tools.admin.ServerConnection.send(ServerConnection.java:114)
	at com.google.appengine.tools.admin.ServerConnection.post(ServerConnection.java:66)
	at com.google.appengine.tools.admin.AppVersionUpload.send(AppVersionUpload.java:345)
	at com.google.appengine.tools.admin.AppVersionUpload.beginTransaction(AppVersionUpload.java:159)
	at com.google.appengine.tools.admin.AppVersionUpload.doUpload(AppVersionUpload.java:68)
	at com.google.appengine.tools.admin.AppAdminImpl.update(AppAdminImpl.java:41)
	at com.google.appengine.eclipse.core.proxy.AppEngineBridgeImpl.deploy(AppEngineBridgeImpl.java:203)
	at com.google.appengine.eclipse.core.deploy.DeployProjectJob.runInWorkspace(DeployProjectJob.java:97)
	at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:38)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:55)
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=riamediator&version=1&
400 Bad Request
Invalid runtime specified.

これはGoogle App EngineでJavaを使用するための認証が済んでいないために発生するエラーです。

以下のURLから認証出来ます。当面は、先着10,000アカウントに限定するみたいなので、使用されたい方はお早めに。

http://appengine.google.com/promo/java_runtime

しばらく(私は5分くらいでした)待つと、メールが来ますので、その後、再度デプロイすると成功するはずです。

Google App Engineでサーバサイドにおける開発言語の主流であるJavaが使えるようになったことで、
Google App Engineの広がりが加速するのは間違いないと思います。

また、今回のアップデートでcronを利用することも出来るようになりました。これで業務アプリケーションへの
適用もしやすくなったのではと思います。ただ、1リクエスト30秒以内という制限があるので、
処理の粒度の工夫がいるとは思います。

Silverlightのクロスドメイン制約

昨今、SilverlightやFlashなどでいろんなWebAPIに接続して、ちょっとしたガジェットを作ってみたいと
思っている方も結構いるのでは思います。

そこで、常に問題なるのがクロスドメイン制約の問題です。Silverlightでは接続する先のドメイン(サブドメインを含む)が
Silverlightアプリがホストされているサーバのドメインと一致してない場合、「SecurityException」が発生してしまいます。

これを回避するためには、ドメインのルートに「クロスドメインポリシーファイル」が置かれている必要があります。
Silverlightは独自の「clientaccesspolicy.xml」に加えて、Flash標準の「crossdomain.xml」にも対応しています。

「crossdomain.xml」に関してはそこそこいろいろなAPIが対応しています。
例えば、実際に試してはいないのですが、「Yahoo API」ではここに置かれています。

しかし、対応していないAPIもあります。メジャーなところで言うと「Google Data API」がそうです。
対応を求めるフォーラムも設置されていますが、今のところ動きはないようです。

Google App Engineを仲介してのクロスドメイン接続

そこで、私が試してみたのが、「Google App Engine」を介したAPIのアクセスです。「Google App Engine」は誰でもが無料で利用できる
クラウドサービスです。

まず、Silverlightアプリを「Google App Engine」にホストします。
(他の記事で、Silverlightアプリを「Google App Engine」にホストする方法を紹介していますので是非、そちらも読んでみてください。)

そして、Silverlightアプリからホストされている「Google App Engine」に接続するコードを書きます。これは
ダウンロード元のドメインに接続することになるので、クロスドメイン制約の問題はありません。
そして、「Google App Engine」から目的のWeb APIに接続するようにします。
つまり、「Silverlight」-「Google App Engine」-「Web API」という3階層のアーキテクチャにすることになります。

以下のコードは「Google App Engine」でYoutubeのおすすめエントリのFeedを取得するコードです。

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import urlfetch

class GetYoutubeFeed(webapp.RequestHandler):
  def get(self):
    result = urlfetch.fetch('http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured?start-index=1')

    self.response.out.write(result.content)

application = webapp.WSGIApplication(
                                     [('/youtube_feed',GetYoutubeFeed)],
                                     debug=True)


[urlfetch]というクラスを利用すると「Google App Engine」からWeb上の他のサービスに接続して、
コンテンツを取得してくることが出来ます。

ポートはHTTP(80)とHTTPS(443)に限定されています。MethodはGET、POST、 PUT、 HEAD、DELETEが使用できます。

そして、何と!、今記事を書きながら、「Google App Engine」
ドキュメントページを開くとJava版のEarly Lookが掲載されています!!

今まで、不慣れなPythonに苦戦してきた私としては、とても嬉しいです。

Google App Engineを介した場合の認証
Google Data APIのほとんどの機能は認証を必要としています。2階層アプリケーションの場合は、クライアントとWebAPIが
直接やり取りするので特に問題ないのですが、3階層モデルになると、一旦中間層(「Google App Engine」)が
ユーザの認証情報を受け取る必要が出てきます。Googleアカウントに関してはかなりいろいろなことが出来るため、
ユーザとしても、サービスを提供する側としても、このデリケートな情報を直接扱うことは是非避けたいところです。

それを回避する方法として、Google Data APIでは「OAuth(オース)」というプロトコルを実装しています。
次回の記事で詳しく書いてみたいと思います。
このブログの英語版を始めました。こちらの方もよろしくお願いします。

英語版ブログ

I started English version of this blog.
If you are an english speaker,Plaease visit here.

My English blog
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。