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