AppsスプレッドシートとApp Engineのデータ交換を完成させる

2013年12月24日(火)
清野 克行

[3] タスク実行サーブレット

キューに追加された実行サーブレットはデフォルトではPOSTメソッドで呼び出され、スプレッドデータのDatastore書き込を行います・サーブレットの指定はリスト4 web.xmlの(2)で行っています。

リスト8 sptodsServlet.java

package com.google.gdata;

import javax.servlet.http.*;
import java.io.IOException;
import java.sql.Connection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.ListQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CustomElementCollection;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.util.AuthenticationException;
import java.util.Properties;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;

@SuppressWarnings("serial")
public class sptodsServlet extends HttpServlet {
  private static final Logger log = Logger.getLogger(sptodsServlet.class.getName());		//(1)
  Connection con  = null;
  @Override 
  public void doPost(HttpServletRequest req, HttpServletResponse resp)
                                                      throws IOException {
    req.setCharacterEncoding("utf-8");
    resp.setContentType("text/plain");
    resp.setCharacterEncoding("utf-8");
    String sheet = req.getParameter("sheet");
    String applicationName = "cyberspace-SpreadsheetSearch-1";
    String username = "xxxxxxxxxxxxxxxxxxxxxx";
    String password = "xxxxxxxxxxxxxx";
    try {
      SpreadsheetService spreadsheetservice = new SpreadsheetService(applicationName);
      spreadsheetservice.setUserCredentials(username, password);
      // 検索対象のスプレッドシートを取得
      FeedURLFactory feedurlfactory = FeedURLFactory.getDefault();
      SpreadsheetQuery spreadsheetquery = 
      new SpreadsheetQuery(feedurlfactory.getSpreadsheetsFeedUrl());
      spreadsheetquery.setTitleQuery(sheet); // 検索対象のスプレッドシート名を指定している
      SpreadsheetFeed spreadsheetfeed = 
      spreadsheetservice.query(spreadsheetquery, SpreadsheetFeed.class);
      SpreadsheetEntry spreadsheetentry = spreadsheetfeed.getEntries().get(0);
      // 検索対象のワークシートを取得
      WorksheetEntry worksheetentry = spreadsheetentry.getDefaultWorksheet();
      //  ワークシート内を検索
      ListQuery listQuery = new ListQuery(worksheetentry.getListFeedUrl());
      ListFeed listFeed = spreadsheetservice.query(listQuery, ListFeed.class); 
      DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); 
      int i = 0;
      for (ListEntry listentry : listFeed.getEntries()) {
        CustomElementCollection customelementcollection = listentry.getCustomElements(); 
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        String moddate = df.format(new Date()); 
        Entity sales = null;
        if (!customelementcollection.getValue("店舗名").equals("売上合計")) { 
          Key saleskey = KeyFactory.createKey
          ("salesym", sheet + customelementcollection.getValue("店舗名")); 
          sales = new Entity(saleskey); 
          sales.setProperty("seq", i++);
        } else {
          Key saleskey = KeyFactory.createKey
          ("salesymtotal", sheet + customelementcollection.getValue("店舗名")); 
          sales = new Entity(saleskey);
        } 
        sales.setProperty("shop", customelementcollection.getValue("店舗名"));
        sales.setProperty("yyyymm", sheet);
        sales.setProperty("food", customelementcollection.getValue("食品"));
        sales.setProperty("electric", customelementcollection.getValue("家電"));
        sales.setProperty("bedding", customelementcollection.getValue("寝具"));
        sales.setProperty("other", customelementcollection.getValue("その他"));
        sales.setProperty("moddate", moddate); 
        ds.put(sales);
        log.severe("{\"flag\": \"" + "Success " + "\"}");
      } 
    } catch (AuthenticationException e) {
      log.severe("{\"flag\": \"" + "認証エラー  : " + e + "\"}");
    } catch (IOException e) {
      log.severe("{\"flag\": \"" + "IOエラー  : " + e + "\"}");
    } catch (Exception e) {
      log.severe("{\"flag\": \"" + "エラー  : " + e + "\"}");
    }
    //////////////////////////////////////////////////////////
    //        メール送信処理l 
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);
    String msgBody = "Drive上スプレッドシートの" + sheet.substring(0, 4) + "年" + sheet.substring(4, 6) +"月度データの、Datastore書き込みが完了しました。\n";
    try {
      Message msg = new MimeMessage(session);
      msg.setFrom(new InternetAddress("sws3gaej4pgm3@gmail.com", "Spread Admin"));
      msg.addRecipient(Message.RecipientType.TO, 
      new InternetAddress("superelmer21@gmail.com", "Spread user"));
      msg.addRecipient(Message.RecipientType.CC, 
      new InternetAddress("sws3gaej4pgm3@gmail.com", "Admin user"));
      msg.setSubject("Spread to Datastore");
      msg.setText(msgBody);
      Transport.send(msg);
      log.severe("{\"flag\": \"" + "Success " + "\"}");
    } catch (AddressException e) {
      log.severe("{\"flag\": \"" + "アドレスエラー  : " + e + "\"}");
    } catch (MessagingException e) {
      log.severe("{\"flag\": \"" + "メッセージングエラー :" + e + "\"}");
    }
  }
}

リスト8はDriveのスプレッドデータを読み取ってDatastoreに書き込むサーブレットです。サーブレットで行われている処理内容は、連載第4回のリスト2にあるビーンズメソッド(addSpreadAllShopToDsYm)と殆ど同じですが、最後にメール送信の処理が追加されています。

[メール送信処理]

Datastoreへの書き込み完了後、メールでの通知を行っています。App Engineのメール送信ではMimeMessageオブジェクトを使用します。リスト8ではインスタンスmsgのメソッドを使用してFROM、TO、CCのアドレスおよび送信メッセージ情報を組み立て、Transportクラスのsend()メソッドでメール送信します。送信されたメールは、メールクライアントがGmailの場合下記のように受信表示されます。

図5:Gmailでのメール受信表示(クリックで拡大)

[ログへの出力]

リスト8のサーブレットはユーザーインタラクションがないため、出力はログ・ファイルに行います。ログ出力ではLog4jを使用していますが、(1)でクラスの名称を Logger インスタンスに設定してLoggerインスタンス(log)を生成、そのあと下記メソッドを使用してログ出力を行っています。

log.info("メッセージ1"); //ログメッセージ1の出力
log.warning("メッセージ2"); //警告(warning)ログメッセージ2の出力
log.severe("メッセージ3"); //エラーログメッセージ3の出力

2. 3 Pull Taskの実行

[1] デフォルトアプリケーションに設定

タスクはデフォルトアプリケーションでなければ実行されません。タスク実行にはプロジェクトをApp EngineのApplicationとしてアップロード後、アップロードしたApplicationのバージョンを、App Engine管理者画面のVersionsから「Make Default」ボタンクリックでデフォルトバージョンに設定します。

[2] 実行確認用HTML

リスト9 pulltask.htm

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>タスクジョブ</title>
</head>
<body><h3>タスクジョブテスト</h3>
<a href="/cron/taskcontrol">pullタスクを実行</a>
</body>
</html>

また、タスクをデフォルトアプリケーションにしても、スケジュールされた時間が来るまで実行されません。しかし、これは開発時にタスク処理の動作確認を行う場合は不便です。リスト9のような簡単なプログラムを作れば、リスト6のtask制御サーブレットをWebクライアントから直接実行することができ、このプログラムを使用してプログラムの動作確認とデバッグを行います。ただしこのプログラムはweb.xmlの下ので /task/* が指定されたままでは実行できないので、この行をコメントアウトして実行します。

スプレッドシートを使用したGoogle DriveとApp Engineのデータ交換を中心とした本連載も今回で終了です。ここまで見てきた内容は数あるGoogleのクラウドを連携する内容でしたが、連載第2回の図18にあるように、App Engineを中心としたクラウド連携は他にもあり、その中でも有力な機能としてApp EngineのDatastoreにあるデータをCloud Storageを介してBigQueryでのビッグデータ解析を行えるようにする連携があります。そしてこの連携を連載で見てきたDriveスプレッドデータとApp Engineの連携と組み合わせれば、大量のスプレッド(およびExcel)データをビッグデータソースとして使用するシステムを構築することもできるようになります。

図6:Googleのクラウド連携で作るビッグデータ処理システム(クリックで拡大)
有限会社サイバースペース
慶應義塾大学工学部電気科卒。日本IBM、日本HPなどにおいて、製造装置業を中心とした業務系/基幹業務系システムのSE/マーケティングや、3階層C/Sアーキテクチャによる社内業務システム開発などに携わる。現在は、Ajax/Web 2.0関連のセミナー講師/コンサルティング、書籍執筆などを行っている。情報処理学会会員。http://www.at21.net/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています