2010年6月25日金曜日

Google Appsがマルチドメインに対応

ずっと前から出来るようになると言われてて、漸く対応したようです。
早速、設定してみましたが、まだドメインのverify中。手順は、ここに書いてある通り。
ただ、ちょっと制限があって、
  1. postiniはシングルドメインでしか使えないため、ドメインエイリアスとして設定しないで、別ドメインを独自で使おうという場合には、postiniを外さないとダメ。→今回はpostini外すのが面倒だったので、エイリアスとして設定。
  2. 既存ドメインのアカウントは、新ドメインとマージされない。→マルチドメイン設定後に新しいアカウントを作る時には、どのドメインか選択可能。
postiniはちょっと残念。そのうち制限外されることを望みます。
対象のEditionは、PremierとEducation。学校に説明に行く時に、説明しやすくなったかも。

2010年6月3日木曜日

データストアが不安定な状態

ここ数週間、GAEのデータストアが不安定な状態が続いています。

管理コンソールが表示されなくなるのも、この問題が原因のようです。

上記の記事では、この不安定な状態の原因を「App Engineの急激な普及」と分析しています。具体的には、ここ半年で2カ月ごとに25%ずつサービスが増加するという状況らしく、それに伴うインフラの整備が追い付いていないようです (さらに、もう2週間くらいは少なくとも不安定な状態になるだろう、と付記しています)。

この状況を受けて、2010/5/31からデータストアのパフォーマンスが安定するまでの間、データストアのCPUに関する課金は行わないという発表がありました (保存しているデータ量に関しては触れられていないので、多分課金されます)。課金が再度行われるようになる際には公式のブログで公開するそうなので、要チェックです。

先ほどキャプチャしたデータストアクエリの遅延に関するグラフ(クエリのみ)です。真っ赤になってます。

2010年5月28日金曜日

Google App Engine for Java[実践]クラウドシステム構築」正誤表 第3章

p.86 リスト3.4.1

package mysite.server;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class LogFilterImpl implements Filter {

    private FilterConfig filterConfig;

    private static final Logger LOG =
            Logger.getLogger(LogFilterImpl.class.getName());

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain filterChain)
            throws IOException, ServletException {
        LOG.info("Log filter processed a " +
                 filterConfig.getInitParameter("logType") +
                 " request");
        filterChain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void destroy() {
        // no-op
    }
}

2010年5月12日水曜日

NSArrayとNSDictionaryのオーナーシップ

Objective-C 2.0ではガベージコレクションが導入されて、メモリ管理がだいぶシンプルになりましたが、iPhone開発ではその恩恵に預かれませんので、色々と気を使います。

NSArrayに要素を追加した時、NSArrayはその要素オブジェクトにretainメッセージを送ってオーナーシップを獲得します。NSArrayが解放された場合、格納されているオブジェクトすべてにreleaseメッセージが送られます。また、格納した要素をNSArrayから削除した場合、その要素にreleaseメッセージが送られます。

なので、こういう書き方をするのはちょいとよろしくありません。

NSArray *array = [[NSArray alloc] initWithObjects:
                     [[Foo alloc] init], [[Bar alloc] init],nil];
// なんらかの処理
[array release];

FooとBarをallocしてinitした時点で、このコードが書かれているオブジェクトがFooとBarそれぞれのオーナーになります。その後、array に追加しているので、array もこの2つの要素のオーナーシップを持ちます。

array を release する事で、Foo と Bar にもreleaseメッセージが送信され、retainCount が減るのですが、コードが書かれてるオブジェクトがまだオーナーシップを握っているため、破棄されません。このコードだと破棄しようもない。困った。

なので、律儀にこう書くのが吉。

Foo *foo = [[Foo alloc] init];
Bar *bar = [[Bar alloc] init];
NSArray *array = [[NSArray alloc] initWithObjects: foo, bar, nil];
[foo release];
[bar release];
// なんらかの処理
[array release];

これじゃどんくさい、という人はautoreleaseにまかせてしまうとか。

NSArray *array = [[NSArray alloc] initWithObjects:
                     [[[Foo alloc] init]autorelease],  [[[Bar alloc] init]autorelease], nil];
// なんらかの処理
[array release];

iPhone環境はautoreleaseは結構まめにやってくれる事がこないだ判ったので、これで安心でしょう。

NSDictionaryの場合、こちらもNSArrayとほぼ同様なのですが、値はそのまま参照してretainメッセージを送りますが、キーはオブジェクトをコピーして保持するそうです。

私はどんくさい方が好みです。実生活ではぐうたらであまり掃除は得意でないんですが。

2010年5月7日金曜日

Java入門サンプル(1)

  • 注意
    • このエントリは実験中のJava入門教材のサンプルです
    • GAE の調子が悪かったりQuotaを超えてたりするとデモがうまく動かないかもしれません
    • 技術背景等はGooseあたりを参照してください

Hello, world!

まずは簡単なプログラムを作成してみましょう。Hello.javaという名前のファイルを作成して次の内容を書き込んでください。

public class Hello {

    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

このプログラムを実行するには、コマンドラインで次のように指定します。

cd (ファイルを作成したフォルダ)
javac Hello.java
java -cp . Hello

すると、「System.out.println("Hello, world!");」のところに書いた「Hello, world!」という文字列が画面に表示されたと思います。

Hello, world!

Javaのプログラムは次のような構造で書きます。

public class (ファイルの名前) {

    public static void main(String[] args) {
        (命令)
    }
}

今回は(命令)の部分に、「System.out.println(...);」という「標準出力に文字列を表示する命令」を書きました。 Javaの文字列は " (ダブルクウォーテーション)で囲んで「"Hello, world!"」のように指定します。

練習

2行以上の出力

(命令)の部分には複数行の命令を書くこともできます。

public class (ファイルの名前) {

    public static void main(String[] args) {
        (命令)
    }
}

Hello2.javaという名前のファイルを作成して次の内容を書き込んでください。

public class Hello2 {

    public static void main(String[] args) {
        System.out.println("こんにちは");
        System.out.println("世界");
    }
}

先ほどと同じように実行してみましょう。

cd (ファイルを作成したフォルダ)
javac Hello2.java
java -cp . Hello2

今回は「こんにちは」と「世界」がそれぞれの行に表示されたと思います。

こんにちは
世界

このように、(命令)の部分には連続してJavaの命令を書くことができます。 今回は「System.out.println(...);」という「標準出力に文字列を表示する命令」を2回使って、2行にわたって文字列を表示しました。

System.out.println("こんにちは");
System.out.println("世界");

また、命令は上から書いた順に実行されます。 1行目は「こんにちは」という文字列を表示して、2行目は「世界」という文字列を表示するプログラムですので、その順番で表示されたと思います。

練習

おまけ

2010年5月6日木曜日

XCode上でXIBファイルを作る時の注意点

大した話でもないんですが。

UIViewControllerのサブクラスのソースファイルをXCode上で新規作成する際に、XIBファイルも同時に作成するオプションがあります。

このオプションを使って出来たXIBファイルは、ソースファイルと同じ場所に出来ます。これではなんなので、XCode上でResourcesへXIBファイルを移動したくなりまして、ドラッグ&ドロップでResources直下へ落としてやったとします。

果たしてそれはXCodeの「グループとファイル」上では上手くいくのですが、ファイルシステム上の位置は変わりません。Classesの中に居っぱなしです。

また、XIBファイルのみを新規作成する場合、「グループとファイル」上のResourcesを選択してその中に作るかと思いますが、そうするとファイルシステム上だとプロジェクトルートにXIBファイルが作成されます。

XCodeの「グループとファイル」ペイン上ではちゃんと仕分けされているので問題はないのですが、どうも気持ち悪いですし、Finderで確認してみようかなという局面の時に要らぬ混乱を来す恐れがあります。

ローカライズなど始めた場合、そこここにxxxx.lprojが出来たりしてどうも落ち着きません。

要は「グループとファイル」上でのドラッグ&ドロップによるファイル移動時にファイルシステムでの移動も可能にする方法があればよいのですが、済みません、見つかってないです。

XCodeのプロジェクトはIDE上で整理出来てればOKだから、ファイルシステム上は野放図でいいよねというポリシーなんでしょうか。

2010年4月30日金曜日

データストアのメンテナンスモードに対応する

Google App Engineのデータストアは定期的にメンテナンスモードに入ります。 メンテナンス中はデータストアが読み取り専用になって一切の書き込みが禁止されます。

Python版にはCapabilityServiceというものが用意されていてこれらを調べるのは簡単ですが、Java版にはまだ用意されていないようです。

Java版で(無理せず)これをテストするには、次のようなサーブレットフィルタを用意してやります。

package com.example;

import java.io.IOException;
import java.util.concurrent.Future;

import javax.servlet.*;

import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.*;

public class MaintenanceFilter implements Filter {

    @Override
    public void init(FilterConfig conf) throws ServletException {
        return;
    }

    @Override
    public void destroy() {
        return;
    }

    @Override
    public void doFilter(
            ServletRequest req,
            ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Delegate<Environment> delegate = ApiProxy.getDelegate();
        try {
            ApiProxy.setDelegate(new MaintenanceDelegate(delegate));
            chain.doFilter(req, res);
        }
        finally {
            ApiProxy.setDelegate(delegate);
        }
    }

    private static class MaintenanceDelegate implements Delegate<Environment> {

        private final Delegate<Environment> delegate;

        MaintenanceDelegate(Delegate<Environment> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void log(Environment env, LogRecord record) {
            this.delegate.log(env, record);
        }

        @Override
        public Future<byte[]> makeAsyncCall(
                Environment env,
                String service,
                String method,
                byte[] bytes,
                ApiConfig config) {
            return this.delegate.makeAsyncCall(env, service, method, bytes, config);
        }

        @Override
        public byte[] makeSyncCall(
                Environment env,
                String service,
                String method,
                byte[] bytes) throws ApiProxyException {
            if (service.equals("datastore_v3")) {
                if (method.equals("Put") || method.equals("Delete")) {
                    throw new CapabilityDisabledException(service, method);
                }
            }
            return this.delegate.makeSyncCall(env, service, method, bytes);
        }
    }
}

これをweb.xmlに登録します。このとき、「/_ah/」以下をフィルタするといろいろ不具合が発生するので、必要最小限にしましょう。

<filter>
    <filter-name>MaintenanceFilter</filter-name>
    <filter-class>com.gluegent.goose.soc.front.util.MaintenanceFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MaintenanceFilter</filter-name>
    <url-pattern>/gadget/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

このフィルタを経由した状態でデータストアに書き込みを行うと、CapabilityDisabledExceptionがスローされるようになります。 例外をキャッチしてメンテナンス中の処理を書いておきましょう。

try {
    // データストアへの書き込み
}
catch (CapabilityDisabledException e) {
    // メンテナンス中の処理
}

メンテナンスモードのときの動作がエミュレーションされるので、この状態で様々なテストを実施します。 なお、このフィルタを残したままサービスインするとひどいことになります。

追記

ご指摘いただきました。 メンテナンスモードのときはDatastoreService.allocateIds()やMemcacheなども動かなくなるので、makeSyncCallメソッドの中でもう少しトラップが必要です。 具体的にはこんな感じ:

@Override
public byte[] makeSyncCall(
        Environment env,
        String service,
        String method,
        byte[] bytes) throws ApiProxyException {
    if (service.equals("datastore_v3")) {
        if (method.equals("Put") ||
                method.equals("Delete") ||
                method.equals("AllocateIds")) {
            throw new CapabilityDisabledException(service, method);
        }
    }
    else if (service.equals("memcache")) {
        throw new CapabilityDisabledException(service, method);
    }
    return this.delegate.makeSyncCall(env, service, method, bytes);
}