SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Google App Engine for Javaを使ってみよう!

Google App Engine for Javaを使ってみよう!
(7)App Engine Cron Service

Google App Engine for Javaを使ってみよう! (7)

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]に設定してください。

プロジェクトの設定(言語設定はUTF-8に)
Project name Cron
Package com.daisukeyamashita.test.cron

プログラムの作成

 データストアに保存するデータクラス(ここではStateDataクラス)を定義します。

com.daisukeyamashita.test.cron.server.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回で利用したヘルパークラスを定義します。

com.daisukeyamashita.test.cron.server.PMFクラス
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通信用のクライアントサイドのソースコードを定義します。

com.daisukeyamashita.test.cron.client.GreetingServiceクラス
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);
}
com.daisukeyamashita.test.cron.client.GreetingServiceAsyncクラス
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通信のサーバサイド部分のソースコードを定義します。ここで、データをデータクラスに設定して、永続化しています。

com.daisukeyamashita.test.cron.server.GreetingServiceImplクラス
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();
		}
	}
}

 クライアント部分のソースコードを定義します。

com.daisukeyamashita.test.cron.client.Cronクラス
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」の部分を自分のメールアドレスに変更してください。

com.daisukeyamashita.test.cron.server.MailServletクラス
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サーブレットにアクセスし、管理者にメールを送信する定義になっています。

war/WEB-INF/cron.xml
<?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>

次のページ
実行する

この記事は参考になりましたか?

Google App Engine for Javaを使ってみよう!連載記事一覧

もっと読む

この記事の著者

山下 大介(ヤマシタ ダイスケ)

  京都大学を中心とした、産官学共同プロジェクトのSOBAプロジェクトに参加後、同プロジェクトで開発したP2P配信によるVoIP技術を2005年に商業化。オープンソース、VoIP、P2P、クラウドコンピューティングに精通。趣味はGoogleの追っかけ。現在は、株式会社SOBAプロジェクト取締役。ブログ:『~ ミネルヴァの梟は黄昏とともに飛び始める ~』所属:株式会社SOBAプロジェクトGoogle Developer Day 2008サポーター, 2009サポーターGoogle App Engine API Expert

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3918 2009/06/18 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー