GAE 開発環境でQuery(“__Stat_Kind__”)を偽装する

Query(“__Stat_Kind__”) はGoogleの環境では使えるけれども開発環境では使えない。getSchemaは開発環境で使えるけれどもGoogleの環境では使えない。両方にmakeSyncCallしたいときはどうするの?っと。そこで開発環境にQuery(“__Stat_Kind__”)が来たときは、内部でgetSchemaしてQueryResultな結果を返すことにしました。仕様を見たわけではないので、確実に動作するかわりません。参考にする場合は注意してください。

1. Query.countEntities()

Query.countEntities()の結果はbyte[]で以下のいずれかになっています。

  • [8][byte count]   (0<=count<2^8)
  • [16][short count] (2^8<=count<2^16)
  • [32][int count]   (2^16<=count<2^32)

最初の1バイトがQueryResponse本体のbit-length、そのあとにEntityの数。例えば次のような関数を考えます。

byte[] statKindCountEntitiesQueryResultEmu(int count){
  if(count<256){
    ByteBuffer buffer = ByteBuffer.allocate(2);
    buffer.put((byte)8);
    buffer.put((byte)count);
    return buffer.array();
  }
  if(count<256*256){
    ByteBuffer buffer = ByteBuffer.allocate(3);
    buffer.put((byte)16);
    buffer.putShort((short)count);
    return buffer.array();
  }
  if(count<256*256*256*256){
    ByteBuffer buffer = ByteBuffer.allocate(5);
    buffer.put((byte)32);
    buffer.putInt(count);
    return buffer.array();
  }
  return new byte[0];
}

2. FetchOptions

FetchOptions fetchOptions;
fetchOptions = FetchOptions.Builder.withOffset(0).limit(10);
List<Entity> list = datastore.prepare(query).asList(fetchOptions);

というリクエストの場合、makeSyncCallサーブレットでは次のようにするとoffsetとlimitの値を取ることができます。

Query query = new DatastorePb.Query();
query.parseFrom(requestBytes);
int offset = query.getOffset();
int limit = query.getLimit();

3. GetSchema

開発環境でkind一覧を取得するために、getSchemaを使用します。

Schema getSchema(){
  GetSchemaRequest schemaRequest = new GetSchemaRequest();
  schemaRequest.setApp(ApiProxy.getCurrentEnvironment().getAppId());
  @SuppressWarnings("unchecked")
  byte[] responseBytes = ApiProxy.getDelegate().makeSyncCall(
      ApiProxy.getCurrentEnvironment(),"datastore_v3",
      "getSchema", schemaRequest.toByteArray());
  Schema schema = new Schema();
  schema.mergeFrom(responseBytes);
  return schema;
}

ここで取得したschemaの中身は

kind <
  key <
    app: "(app-id)"
    path <
      Element {
        type: "(kind-name)"
      }
    >
  >
  entity_group <

  >
  property <
    ...
  >
  property <
    ...
  >
  ...
>

4. Query(“__Stat_Kind__”)

実際の環境でQuery(“__Stat_Kind__”)を実行すると、以下の内容がkindの数だけ続きます。

result <
  key <
    app: "(app-id)"
    path <
      Element {
        type: "__Stat_Kind__"
        name: "(kind-name)"
      }
    >
  >
  entity_group <
    Element {
      type: "__Stat_Kind__"
      name: "(kind-name)"
    }
  >
  property <
    name: "bytes"
    value <
      int64Value: ...
    >
    multiple: false
  >
  property <
    name: "count"
    value <
      int64Value: ...
    >
    multiple: false
  >
  property <
    meaning: 7
    name: "timestamp"
    value <
      int64Value: 0x...
    >
    multiple: false
  >
  property <
    name: "kind_name"
    value <
      stringValue: "(kind-name)"
    >
    multiple: false
  >
>

結果をこの形式で返せばいいので、QueryResultを組み立てる関数は

byte[] statKindQueryResultEmu(Schema schema, int offset, int limit){

  QueryResult queryResult = new DatastorePb.QueryResult();

  List<EntityProto> kinds = schema.kinds();

  if(offset<0) offset = 0;
  if(limit<0) limit = kinds.size();

  EntityProto entityProto;
  Element statKindElm;
  String kind;
  for (int i = offset; i < offset+limit; i++) {
    if(i>=kinds.size())break;
    entityProto = kinds.get(i);
    kind = entityProto.getKey().getPath().getElement(0).getType();

    statKindElm = new OnestoreEntity.Path.Element();
    statKindElm.setType("__Stat_Kind__");
    statKindElm.setName(kind);

    //set key
    entityProto.getKey().getPath().clearElement();
    entityProto.getKey().setApp(ApiProxy.getCurrentEnvironment().getAppId());
    entityProto.getKey().getPath().addElement(statKindElm);
    entityProto.getEntityGroup().addElement(statKindElm);

    //set kind name
    Property kindProperty = entityProto.addProperty();
    kindProperty.setName("kind_name");
    kindProperty.setValue(new PropertyValue().setStringValue(kind));

    com.google.appengine.api.datastore.Query countQuery;
    countQuery = new com.google.appengine.api.datastore.Query(kind);
    countQuery.setKeysOnly();

    DatastoreService service;
    service = DatastoreServiceFactory.getDatastoreService();
    int entitySize = service.prepare(countQuery).countEntities();

    //set count
    Property countProperty = entityProto.addProperty();
    countProperty.setName("count");
    countProperty.setValue(new PropertyValue().setInt64Value(entitySize));

    queryResult.addResult(entityProto);
  }
  queryResult.setKeysOnly(false);
  queryResult.setMoreResults(false);

  return queryResult.toByteArray();
}

以上のことから開発環境のmakeSyncCallサーブレットでQuery(“__Stat_Kind__”)を偽装する方法は次のようになります。ただQuery(“__Stat_Kind__”)とQuery(“__Stat_Kind__”).countEntities()のリクエストはバイト単位で同じものだったので、リクエストからこれらを区別する方法がわかりませんでした。そこで実際のリクエストをApiProxyに投げて、その結果をもとに処理を分けています。

String environment = System.getProperty(
    "com.google.appengine.runtime.environment");

if(environment.equals("Development")){

  Query query = new DatastorePb.Query();
  query.parseFrom(requestBytes);

  if(query.getKind().equals("__Stat_Kind__")){

    Schema schema = getSchema();

    byte[] bytes = ApiProxy.makeSyncCall(
        serviceName, methodName, requestBytes);
    QueryResult qr = new QueryResult();
    qr.parseFrom(bytes);

    byte[] responseBytes;
    if(qr.hasCursor()){
      responseBytes = statKindQueryResultEmu(
        schema, query.getOffset(), query.getLimit());
    }else{
      responseBytes = statKindCountEntitiesQueryResultEmu(
        schema.kinds().size());
    }

    resp.setContentType("application/octet-stream");
    resp.addHeader("Content-Length", String.valueOf(responseBytes.length));
    resp.getOutputStream().write(responseBytes);
    resp.getOutputStream().flush();
    return;
  }
}
t

おすすめ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です