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

Commit

Permalink
feat: REST Gapic (REGAPIC) Support (#1177)
Browse files Browse the repository at this point in the history
  • Loading branch information
vam-google committed Oct 12, 2020
1 parent b856351 commit 12b18ee
Show file tree
Hide file tree
Showing 16 changed files with 899 additions and 10 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Expand Up @@ -132,6 +132,8 @@ subprojects {
'maven.io_grpc_grpc_protobuf': "io.grpc:grpc-protobuf:${libraries['version.io_grpc']}",
'maven.io_grpc_grpc_netty_shaded': "io.grpc:grpc-netty-shaded:${libraries['version.io_grpc']}",
'maven.io_grpc_grpc_alts': "io.grpc:grpc-alts:${libraries['version.io_grpc']}",
'maven.com_google_protobuf': "com.google.protobuf:protobuf-java:${libraries['version.com_google_protobuf']}",
'maven.com_google_protobuf_java_util': "com.google.protobuf:protobuf-java-util:${libraries['version.com_google_protobuf']}"
])
}

Expand Down
2 changes: 2 additions & 0 deletions gax-httpjson/BUILD.bazel
Expand Up @@ -20,6 +20,8 @@ _COMPILE_DEPS = [
"@com_google_auto_value_auto_value_annotations//jar",
"@com_google_http_client_google_http_client_jackson2//jar",
"@javax_annotation_javax_annotation_api//jar",
"@com_google_protobuf//:protobuf_java",
"@com_google_protobuf_java_util//jar",
"//gax:gax",
]

Expand Down
2 changes: 2 additions & 0 deletions gax-httpjson/build.gradle
Expand Up @@ -5,6 +5,8 @@ project.version = "0.76.1" // {x-version-update:gax-httpjson:current}

dependencies {
compile project(':gax'),
libraries['maven.com_google_protobuf'],
libraries['maven.com_google_protobuf_java_util'],
libraries['maven.com_google_code_gson_gson'],
libraries['maven.com_google_guava_guava'],
libraries['maven.com_google_code_findbugs_jsr305'],
Expand Down
Expand Up @@ -32,12 +32,15 @@
import com.google.auto.value.AutoValue;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/** Utility class to parse {@link ApiMessage}s from HTTP responses. */
@AutoValue
Expand Down Expand Up @@ -91,8 +94,13 @@ public ResponseT parse(InputStream httpResponseBody) {
return null;
} else {
Type responseType = getResponseInstance().getClass();
return getResponseMarshaller()
.fromJson(new InputStreamReader(httpResponseBody), responseType);
try {
return getResponseMarshaller()
.fromJson(
new InputStreamReader(httpResponseBody, StandardCharsets.UTF_8), responseType);
} catch (JsonIOException | JsonSyntaxException e) {
throw new RestSerializationException(e);
}
}
}

Expand Down
@@ -0,0 +1,41 @@
/*
* Copyright 2020 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.api.core.BetaApi;

/**
* A functional interface to be implemented for each request message to extract specific fields from
* it. For advanced usage only.
*/
@BetaApi
public interface FieldsExtractor<RequestT, ParamsT> {
ParamsT extract(RequestT request);
}
Expand Up @@ -103,7 +103,8 @@ HttpRequest createHttpRequest() throws IOException {
}

// Populate URL path and query parameters.
GenericUrl url = new GenericUrl(getEndpoint() + requestFormatter.getPath(getRequest()));
String endpoint = normalizeEndpoint(getEndpoint());
GenericUrl url = new GenericUrl(endpoint + requestFormatter.getPath(getRequest()));
Map<String, List<String>> queryParams = requestFormatter.getQueryParamNames(getRequest());
for (Entry<String, List<String>> queryParam : queryParams.entrySet()) {
if (queryParam.getValue() != null) {
Expand All @@ -120,12 +121,26 @@ HttpRequest createHttpRequest() throws IOException {
return httpRequest;
}

// This will be frequently executed, so avoiding using regexps if not necessary.
private String normalizeEndpoint(String endpoint) {
String normalized = endpoint;
// Set protocol as https by default if not set explicitly
if (!normalized.contains("://")) {
normalized = "https://" + normalized;
}

if (normalized.charAt(normalized.length() - 1) != '/') {
normalized += '/';
}

return normalized;
}

@Override
public void run() {
try {
HttpRequest httpRequest = createHttpRequest();
HttpResponse httpResponse = httpRequest.execute();

if (!httpResponse.isSuccessStatusCode()) {
ApiExceptionFactory.createException(
null,
Expand Down
Expand Up @@ -33,19 +33,26 @@
import com.google.api.core.InternalExtensionOnly;
import java.io.InputStream;

/** Interface for classes that parse parts of Http responses into the parameterized message type. */
/** Interface for classes that parse parts of HTTP responses into the parameterized message type. */
@InternalExtensionOnly
public interface HttpResponseParser<MessageFormatT> {

/* Parse the http body content JSON stream into the MessageFormatT.
/**
* Parse the http body content JSON stream into the MessageFormatT.
*
* @param httpContent the body of an http response. */
* @param httpContent the body of an HTTP response
* @throws RestSerializationException if failed to parse the {@code httpContent} to a valid {@code
* MessageFormatT}
*/
MessageFormatT parse(InputStream httpContent);

/* Serialize an object into an HTTP body, which is written out to output.
/**
* Serialize an object into an HTTP body, which is written out to output.
*
* @param response the object to serialize.
* @param output the output stream to append the serialization to. */
* @param response the object to serialize
* @throws RestSerializationException if failed to serialize {@code response} to a valid {@code
* String} representation
*/
@InternalApi
String serialize(MessageFormatT response);
}
@@ -0,0 +1,123 @@
/*
* Copyright 2020 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.api.core.BetaApi;
import com.google.api.pathtemplate.PathTemplate;
import com.google.protobuf.Message;
import java.util.List;
import java.util.Map;

/** Creates parts of a HTTP request from a protobuf message. */
@BetaApi
public class ProtoMessageRequestFormatter<RequestT extends Message>
implements HttpRequestFormatter<RequestT> {

private final FieldsExtractor<RequestT, String> requestBodyExtractor;
// Using of triple nested generics (which is not pretty) is predetermined by the
// Map<String, List<String>> returned value type of the getQueryParamNames interface method
// implemented by this class.
private final FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor;
private final PathTemplate pathTemplate;
private final FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor;

private ProtoMessageRequestFormatter(
FieldsExtractor<RequestT, String> requestBodyExtractor,
FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor,
PathTemplate pathTemplate,
FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor) {
this.requestBodyExtractor = requestBodyExtractor;
this.queryParamsExtractor = queryParamsExtractor;
this.pathTemplate = pathTemplate;
this.pathVarsExtractor = pathVarsExtractor;
}

public static <RequestT extends Message>
ProtoMessageRequestFormatter.Builder<RequestT> newBuilder() {
return new Builder<>();
}

/* {@inheritDoc} */
@Override
public Map<String, List<String>> getQueryParamNames(RequestT apiMessage) {
return queryParamsExtractor.extract(apiMessage);
}

/* {@inheritDoc} */
@Override
public String getRequestBody(RequestT apiMessage) {
return requestBodyExtractor.extract(apiMessage);
}

/* {@inheritDoc} */
@Override
public String getPath(RequestT apiMessage) {
return pathTemplate.instantiate(pathVarsExtractor.extract(apiMessage));
}

/* {@inheritDoc} */
@Override
public PathTemplate getPathTemplate() {
return pathTemplate;
}

// This has class has compound setter methods (multiple arguments in setters), that is why not
// using @AutoValue.
public static class Builder<RequestT extends Message> {
private FieldsExtractor<RequestT, String> requestBodyExtractor;
private FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor;
private String path;
private FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor;

public Builder<RequestT> setRequestBodyExtractor(
FieldsExtractor<RequestT, String> requestBodyExtractor) {
this.requestBodyExtractor = requestBodyExtractor;
return this;
}

public Builder<RequestT> setQueryParamsExtractor(
FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor) {
this.queryParamsExtractor = queryParamsExtractor;
return this;
}

public Builder<RequestT> setPath(
String path, FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor) {
this.path = path;
this.pathVarsExtractor = pathVarsExtractor;
return this;
}

public ProtoMessageRequestFormatter<RequestT> build() {
return new ProtoMessageRequestFormatter<>(
requestBodyExtractor, queryParamsExtractor, PathTemplate.create(path), pathVarsExtractor);
}
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2020 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.api.core.BetaApi;
import com.google.protobuf.Message;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/** The implementation of {@link HttpResponseParser} which works with protobuf messages. */
@BetaApi
public class ProtoMessageResponseParser<ResponseT extends Message>
implements HttpResponseParser<ResponseT> {

private final ResponseT defaultInstance;

private ProtoMessageResponseParser(ResponseT defaultInstance) {
this.defaultInstance = defaultInstance;
}

public static <RequestT extends Message>
ProtoMessageResponseParser.Builder<RequestT> newBuilder() {
return new ProtoMessageResponseParser.Builder<>();
}

/* {@inheritDoc} */
@Override
public ResponseT parse(InputStream httpContent) {
return ProtoRestSerializer.<ResponseT>create()
.fromJson(httpContent, StandardCharsets.UTF_8, defaultInstance.newBuilderForType());
}

/* {@inheritDoc} */
@Override
public String serialize(ResponseT response) {
return ProtoRestSerializer.create().toJson(response);
}

// Convert to @AutoValue if this class gets more complicated
public static class Builder<ResponseT extends Message> {
private ResponseT defaultInstance;

public Builder<ResponseT> setDefaultInstance(ResponseT defaultInstance) {
this.defaultInstance = defaultInstance;
return this;
}

public ProtoMessageResponseParser<ResponseT> build() {
return new ProtoMessageResponseParser<>(defaultInstance);
}
}
}

0 comments on commit 12b18ee

Please sign in to comment.