App Engine Cron Serviceを利用する
App Engine Cron Serviceは管理コンソールのVersionsでデフォルトアプリケーションに設定されているアプリケーションのcron.xmlのみが有効になります。
今回のサンプルは、「データストアに保存したステータスを管理者に毎分メールする」というものなので、サンプルをデプロイしたまま放置するとメールボックスに大量のメールが来てしまいます。サンプルの動作確認が終わったらすぐに、デフォルトアプリケーションを切り替えて、App Engine Cron Serviceを停止できるように、先にEclipseで適当なGoogle App Engine用のWeb Applicationプロジェクトを作成してデプロイしておいてください。
プロジェクトの作成
以下の設定でGWTプロジェクトを作成してください。GWTプロジェクトの作成手順については、第1回の解説を参照してください。今回は日本語(2バイトコード)を扱いたいので、言語設定を必ず[UTF-8]に設定してください。
Project name | Cron |
Package | com.daisukeyamashita.test.cron |
プログラムの作成
データストアに保存するデータクラス(ここではStateData
クラス)を定義します。
package com.daisukeyamashita.test.cron.server; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class StateData { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id; @Persistent private String name; public StateData(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } }
データの読み書きを簡単にするために、第5回で利用したヘルパークラスを定義します。
package com.daisukeyamashita.test.cron.server; import javax.jdo.JDOHelper; import javax.jdo.PersistenceManagerFactory; public class PMF { private static final PersistenceManagerFactory pmfInstance = JDOHelper .getPersistenceManagerFactory("transactions-optional"); private PMF() { } public static PersistenceManagerFactory get() { return pmfInstance; } }
続いて、GWTのAjax通信用のクライアントサイドのソースコードを定義します。
package com.daisukeyamashita.test.cron.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; /** * The client side stub for the RPC service. */ @RemoteServiceRelativePath("greet") public interface GreetingService extends RemoteService { void store(String name); }
package com.daisukeyamashita.test.cron.client; import com.google.gwt.user.client.rpc.AsyncCallback; /** * The async counterpart of <code>GreetingService</code>. */ public interface GreetingServiceAsync { void store(String name, AsyncCallback callback); }
GWTのAjax通信のサーバサイド部分のソースコードを定義します。ここで、データをデータクラスに設定して、永続化しています。
package com.daisukeyamashita.test.cron.server; import java.util.List; import javax.jdo.PersistenceManager; import com.daisukeyamashita.test.cron.client.GreetingService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; /** * The server side implementation of the RPC service. */ @SuppressWarnings("serial") public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService { @SuppressWarnings("unchecked") public void store(String name) { PersistenceManager pm = PMF.get().getPersistenceManager(); String query = "select from " + StateData.class.getName(); List<StateData> list = (List<StateData>) pm.newQuery(query).execute(); StateData data; if (list.size() == 0) { data = new StateData(name); } else { data = list.get(0); data.setName(name); } try { pm.makePersistent(data); } finally { pm.close(); } } }
クライアント部分のソースコードを定義します。
package com.daisukeyamashita.test.cron.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; /** * Entry point classes define <code>onModuleLoad()</code>. */ public class Cron implements EntryPoint { /** * Create a remote service proxy to talk to the server-side Greeting service. */ private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class); /** * This is the entry point method. */ public void onModuleLoad() { final TextBox tbName = new TextBox(); tbName.setText("晴れ"); final Button btnStore = new Button("store"); final DialogBox dialogBox = new DialogBox(); dialogBox.setText("Remote Procedure Call"); dialogBox.setAnimationEnabled(true); final Button closeButton = new Button("Close"); // We can set the id of a widget by accessing its Element closeButton.getElement().setId("closeButton"); final Label lblServerResponse = new Label(); VerticalPanel dialogVPanel = new VerticalPanel(); dialogVPanel.addStyleName("dialogVPanel"); dialogVPanel.add(lblServerResponse); dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); dialogVPanel.add(closeButton); dialogBox.setWidget(dialogVPanel); // Add a handler to close the DialogBox closeButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { dialogBox.hide(); btnStore.setEnabled(true); btnStore.setFocus(true); } }); btnStore.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { btnStore.setEnabled(false); btnStore.setFocus(false); greetingService.store(tbName.getText(), new AsyncCallback() { public void onSuccess(Object result) { lblServerResponse.setText("Success"); dialogBox.center(); } public void onFailure(Throwable caught) { lblServerResponse.setText("Failure"); caught.printStackTrace(); } }); } }); RootPanel.get("nameFieldContainer").add(tbName); RootPanel.get("sendButtonContainer").add(btnStore); } }
App Engine Cron Serviceで動かすプログラムです。「test@example.com」の部分を自分のメールアドレスに変更してください。
package com.daisukeyamashita.test.cron.server; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Properties; import javax.jdo.PersistenceManager; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MailServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PersistenceManager pm = PMF.get().getPersistenceManager(); String query = "select from " + StateData.class.getName(); List<StateData> list = (List<StateData>) pm.newQuery(query).execute(); if (list.size() == 0) { return; } StateData data = list.get(0); Properties props = new Properties(); Session session = Session.getDefaultInstance(props, null); try { InternetAddress address = new InternetAddress("test@example.com", "管理者", "iso-2022-jp"); MimeMessage message = new MimeMessage(session); message.setFrom(address); // 送信元アドレス message.addRecipient(Message.RecipientType.TO, address); // あて先アドレス message.setSubject("Google App Engineからのメールです", "ISO-2022-JP"); // タイトル message.setText("現在のステータスは『" + data.getName() + "』です。"); // メール本文 Transport.send(message); } catch (UnsupportedEncodingException e) { //error e.printStackTrace(); } catch (MessagingException e) { //error e.printStackTrace(); } } }
「war/WEB-INF/web.xml」の一番下にある</web-app>
タグの直前に以下のタグを追加してください。
<servlet> <servlet-name>mailServlet</servlet-name> <servlet-class>com.daisukeyamashita.test.cron.server.MailServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>mailServlet</servlet-name> <url-pattern>/cron/mail</url-pattern> </servlet-mapping>
最後に、「war/WEB-INF/cron.xml」を新規作成します。最初に書いたように、毎分/cron/mailサーブレットにアクセスし、管理者にメールを送信する定義になっています。
<?xml version="1.0" encoding="UTF-8"?> <cronentries> <cron> <url>/cron/mail</url> <description>every 1 minutes</description> <schedule>every 1 minutes</schedule> </cron> </cronentries>