Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

Commit

Permalink
Add fieldmask to JSON serialization functions (#537)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreamlin committed May 16, 2018
1 parent fe09903 commit 8f9d6ce
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 83 deletions.
Expand Up @@ -31,19 +31,19 @@

import com.google.api.core.BetaApi;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/* An interface for message classes. */
@BetaApi
public interface ApiMessage {
/* For each field name in fieldNames, fetch that field's List<String> value. */
Map<String, List<String>> populateFieldsInMap(Set<String> fieldNames);

/* Get the String value of a field in this message. */
@Nullable
String getFieldStringValue(String fieldName);
Object getFieldValue(String fieldName);

/* List of names of fields to include in the serialized request body. */
@Nullable
List<String> getFieldMask();

/* If this is a Request object, return the inner ApiMessage that represents the body
* of the request; else return null. */
Expand Down
Expand Up @@ -33,11 +33,9 @@
import com.google.api.pathtemplate.PathTemplate;
import com.google.api.resourcenames.ResourceNameFactory;
import com.google.auto.value.AutoValue;
import com.google.gson.Gson;
import com.google.common.collect.Lists;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
Expand All @@ -62,37 +60,14 @@ public abstract class ApiMessageHttpRequestFormatter<RequestT extends ApiMessage
@Override
public abstract PathTemplate getPathTemplate();

protected abstract Gson getRequestMarshaller();

private static <RequestT extends ApiMessage> ApiMessageHttpRequestFormatter<RequestT> create(
final RequestT requestInstance,
Set<String> queryParams,
String resourceNameField,
ResourceNameFactory resourceNameFactory,
PathTemplate pathTemplate) {

final Gson baseGson = new GsonBuilder().create();

TypeAdapter requestTypeAdapter =
new TypeAdapter<RequestT>() {
@Override
public void write(JsonWriter out, RequestT value) {
baseGson.toJson(value, requestInstance.getClass(), out);
}

@Override
public RequestT read(JsonReader in) {
return null;
}
};

Gson requestMarshaller =
new GsonBuilder()
.registerTypeAdapter(requestInstance.getClass(), requestTypeAdapter)
.create();

return new AutoValue_ApiMessageHttpRequestFormatter<>(
resourceNameField, resourceNameFactory, queryParams, pathTemplate, requestMarshaller);
resourceNameField, resourceNameFactory, queryParams, pathTemplate);
}

public static <RequestT extends ApiMessage>
Expand All @@ -104,7 +79,23 @@ ApiMessageHttpRequestFormatter.Builder<RequestT> newBuilder() {
public Map<String, List<String>> getQueryParamNames(RequestT apiMessage) {
Set<String> paramNames = getQueryParamNames();
Map<String, List<String>> queryParams = new HashMap<>();
Map<String, List<String>> nullableParams = apiMessage.populateFieldsInMap(paramNames);
Map<String, List<String>> nullableParams = new HashMap<>();
for (String paramName : paramNames) {
Object paramValue = apiMessage.getFieldValue(paramName);
List<String> valueList;
if (paramValue == null) {
continue;
}
if (paramValue instanceof List) {
valueList = new ArrayList<>();
for (Object val : (List<Object>) paramValue) {
valueList.add(val.toString());
}
} else {
valueList = Lists.newArrayList(paramValue.toString());
}
nullableParams.put(paramName, valueList);
}
Iterator<Map.Entry<String, List<String>>> iterator = nullableParams.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<String>> pair = iterator.next();
Expand All @@ -118,10 +109,15 @@ public Map<String, List<String>> getQueryParamNames(RequestT apiMessage) {
@Override
public String getRequestBody(ApiMessage apiMessage) {
ApiMessage body = apiMessage.getApiMessageRequestBody();
if (body != null) {
return getRequestMarshaller().toJson(body);
if (body == null) {
return null;
}
GsonBuilder requestMarshaller = new GsonBuilder().serializeNulls();
if (apiMessage.getFieldMask() != null) {
requestMarshaller.registerTypeAdapter(
body.getClass(), new FieldMaskedSerializer(apiMessage.getFieldMask()));
}
return null;
return requestMarshaller.create().toJson(body);
}

@Override
Expand All @@ -131,29 +127,24 @@ public String getPath(RequestT apiMessage) {
}

private Map<String, String> getPathParams(RequestT apiMessage) {
String resourceNamePath = apiMessage.getFieldStringValue(getResourceNameField());
if (resourceNamePath == null) {
Object fieldValue = apiMessage.getFieldValue(getResourceNameField());
if (fieldValue == null) {
throw new IllegalArgumentException(
String.format(
"Resource name field %s is null in message object.", getResourceNameField()));
}
String resourceNamePath = fieldValue.toString();
return getResourceNameFactory().parse(resourceNamePath).getFieldValuesMap();
}

public static class Builder<RequestT extends ApiMessage> {
private RequestT requestInstance;
private String resourceNameField;
private ResourceNameFactory resourceNameFactory;
private Set<String> queryParams;
private PathTemplate pathTemplate;

private Builder() {}

public Builder<RequestT> setRequestInstance(RequestT requestInstance) {
this.requestInstance = requestInstance;
return this;
}

public Builder<RequestT> setResourceNameField(String resourceNameField) {
this.resourceNameField = resourceNameField;
return this;
Expand All @@ -176,7 +167,7 @@ public Builder<RequestT> setQueryParams(Set<String> queryParams) {

public ApiMessageHttpRequestFormatter<RequestT> build() {
return ApiMessageHttpRequestFormatter.create(
requestInstance, queryParams, resourceNameField, resourceNameFactory, pathTemplate);
queryParams, resourceNameField, resourceNameFactory, pathTemplate);
}
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2018 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.api.gax.httpjson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.List;

/** JSON Serializer for messages with a field mask to selectively serialize fields. */
public class FieldMaskedSerializer implements JsonSerializer<ApiMessage> {
private final List<String> fieldMask;

public FieldMaskedSerializer(List<String> fieldMask) {
this.fieldMask = fieldMask;
}

@Override
public JsonElement serialize(
ApiMessage requestBody, Type typeOfSrc, JsonSerializationContext context) {
Gson gson = new GsonBuilder().serializeNulls().create();
if (fieldMask == null) {
return gson.toJsonTree(requestBody, typeOfSrc);
}
JsonObject jsonObject = new JsonObject();
for (String fieldName : fieldMask) {
Object fieldValue = requestBody.getFieldValue(fieldName);
if (fieldValue != null) {
jsonObject.add(fieldName, gson.toJsonTree(fieldValue, fieldValue.getClass()));
} else {
// TODO(andrealin): This doesn't distinguish between the non-existence of a field
// and a field value being null.
jsonObject.add(fieldName, JsonNull.INSTANCE);
}
}

return jsonObject;
}
}

0 comments on commit 8f9d6ce

Please sign in to comment.