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>
