2009年8月29日土曜日

#appengine java でローカル環境のデータファイルをほげってみる

目的はKind名の一覧を取得する事。Kind名さえわかれば、後は普通にDatastoreServiceFactory#getDatastoreService()を使って、Entityを読み込む事ができて、Entityが読み込めればそこから全propertyを取得できたりして、色々な事ができそう。ローカルからAppEngineにアップロードする時にも使えそうだし。Eclipse用にLocalDatastoreViewerPluginとかも作れそうだし。

……っていうエントリを書くつもりだったんだけど、SchemaやらEntityProtoやら色々要素が多すぎて、説明するのが大変に面倒になって来たので、ユーティリティクラスのコードだけ晒す事にしますw ゴメンナサイ、ゴメンナサイ。

AppEngineの起動と終了

Datastore周りの単体テストをする時とほぼおんなじ。

static final String DATASTORE_V3 = "datastore_v3";

static ApiProxy.Environment setUpDatastoreService(final String appId, final String versionId,
    String applicationFolder) {
  ApiProxy.Environment environment;
  ApiProxy.setEnvironmentForCurrentThread(environment = new ApiProxy.Environment() {
    public String getAppId() { return appId; }
    public String getVersionId() { return versionId; }
    public String getRequestNamespace() { return ""; }
    public String getAuthDomain() { return "hoge.com"; }
    public boolean isLoggedIn() { return true; }
    public String getEmail() { return "fuga@hoge.com"; }
    public boolean isAdmin() { return false; }
    public Map<String, Object> getAttributes() {
      Map<String, Object> map = new HashMap<String, Object>();
      return map;
    }
  });
  ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(applicationFolder)) {});
  return environment;
}

static void tearDownDatastoreService() {
  ((LocalDatastoreService) ((ApiProxyLocalImpl) ApiProxy.getDelegate())
      .getService(DATASTORE_V3)).stop();
  ApiProxy.setDelegate(null);
  ApiProxy.setEnvironmentForCurrentThread(null);
}

Kind名の一覧を取得する

LocalDatastoreService#getSchema()ってのがあります。スキーマレスなのにgetSchame()とはなんのこっちゃってカンジですが、Datastoreのビューアが使っているのもこの情報です。

public static String[] getKinds(Environment environment) {
  LocalDatastoreService datastoreService = (LocalDatastoreService) ((ApiProxyLocalImpl) ApiProxy
      .getDelegate()).getService(DATASTORE_V3);
  Schema schema = datastoreService.getSchema(null, (new StringProto()).setValue(environment
      .getAppId()));
  List<EntityProto> entityProtoList = schema.kinds();
  List<String> kindList = new ArrayList<String>(entityProtoList.size());
  for (EntityProto entityProto : entityProtoList) {
    List<?> path = entityProto.getKey().getPath().elements();
    Element element = (Element) path.get(path.size() - 1);
    kindList.add(element.getType());
  }
  return kindList.toArray(new String[0]);
}

特定のproperty名の一覧を取得する

Kind名がわれば実行時に取得できるので、あんまり必要無いかも。ちなみに、JDOを使っているとPOJOで定義した覚えが無いpropertyが混ざってくると思います。楽観的排他制御とか、ListPropertyのExtentで定義した制御の為にJDOが独自のpropertyを付加したものです。また、残念な事にインデックス対象でないpropertyは一覧から取得できません。

public static String[] getProperties(Environment environment, String kind) {
  LocalDatastoreService datastoreService = (LocalDatastoreService) ((ApiProxyLocalImpl) ApiProxy
      .getDelegate()).getService(DATASTORE_V3);
  Schema schema = datastoreService.getSchema(null, (new StringProto()).setValue(environment
      .getAppId()));
  List<EntityProto> entityProtoList = schema.kinds();
  for (EntityProto entityProto : entityProtoList) {
    List<?> path = entityProto.getKey().getPath().elements();
    Element element = (Element) path.get(path.size() - 1);
    String type = element.getType();
    if (kind.equals(type)) {
      List<Property> properties = entityProto.propertys();
      String[] propertyNames = new String[properties.size()];
      for (int i = 0; i < propertyNames.length; i++) {
        propertyNames[i] = properties.get(i).getName();
      }
      return propertyNames;
    }
  }
  throw new RuntimeException("kind \"" + kind + "\" is not found.");
}

使い方

SDKのコンテナを使った環境で使用しているデータファイル(war/WEB-INF/appengine-generated/local_db.bin)を使いたい場合は、以前書いたエントリ「#appengine のテスト用初期データを作成する」を参考に、アプリケーションのversionIdを確認しておく必要があります。

@Test public void test() {
  Environment environment = setUpDatastoreService("shin1ogawa-app", "versionid.1", "war");
  try {
    String[] kinds = DatastoreUtil.getKinds(environment);
    for (String kind : kinds) {
      System.out.println(kind);
      String[] properties = DatastoreUtil.getProperties(environment, kind);
      for (String property : properties) {
        System.out.println("  " + property);
      }
    }
  } finally {
    tearDownDatastoreService();
  }
}

importの宣言

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;

import com.google.appengine.api.datastore.Blob;
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.dev.LocalDatastoreService;
import com.google.appengine.tools.development.ApiProxyLocalImpl;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiBasePb.StringProto;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.apphosting.api.DatastorePb.Schema;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import com.google.storage.onestore.v3.OnestoreEntity.Property;
import com.google.storage.onestore.v3.OnestoreEntity.Path.Element;

コメントを投稿