Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static com.sap.cloud.sdk.services.openapi.apache.apiclient.DefaultApiResponseHandler.isJsonMime;
import static lombok.AccessLevel.PRIVATE;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
Expand All @@ -28,6 +29,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -68,6 +70,7 @@
import lombok.Getter;
import lombok.ToString;
import lombok.With;
import lombok.val;

/**
* API client for executing HTTP requests using Apache HttpClient 5.
Expand Down Expand Up @@ -348,77 +351,6 @@ private ContentType getContentType( @Nonnull final String headerValue )
}
}

/**
* Serialize the given Java object into string according the given Content-Type (only JSON is supported for now).
*
* @param obj
* Object
* @param contentType
* Content type
* @param formParams
* Form parameters
* @return Object
* @throws OpenApiRequestException
* API exception
*/
@Nonnull
private HttpEntity serialize(
@Nullable final Object obj,
@Nonnull final Map<String, Object> formParams,
@Nonnull final ContentType contentType )
throws OpenApiRequestException
{
final String mimeType = contentType.getMimeType();
if( isJsonMime(mimeType) ) {
try {
return new StringEntity(
objectMapper.writeValueAsString(obj),
contentType.withCharset(StandardCharsets.UTF_8));
}
catch( JsonProcessingException e ) {
throw new OpenApiRequestException(e);
}
} else if( mimeType.equals(ContentType.MULTIPART_FORM_DATA.getMimeType()) ) {
final MultipartEntityBuilder multiPartBuilder = MultipartEntityBuilder.create();
for( final Entry<String, Object> paramEntry : formParams.entrySet() ) {
final Object value = paramEntry.getValue();
if( value instanceof File file ) {
multiPartBuilder.addBinaryBody(paramEntry.getKey(), file);
} else if( value instanceof byte[] byteArray ) {
multiPartBuilder.addBinaryBody(paramEntry.getKey(), byteArray);
} else {
final Charset charset = contentType.getCharset();
if( charset != null ) {
final ContentType customContentType =
ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), charset);
multiPartBuilder
.addTextBody(
paramEntry.getKey(),
parameterToString(paramEntry.getValue()),
customContentType);
} else {
multiPartBuilder.addTextBody(paramEntry.getKey(), parameterToString(paramEntry.getValue()));
}
}
}
return multiPartBuilder.build();
} else if( mimeType.equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) ) {
final List<NameValuePair> formValues = new ArrayList<>();
for( final Entry<String, Object> paramEntry : formParams.entrySet() ) {
formValues.add(new BasicNameValuePair(paramEntry.getKey(), parameterToString(paramEntry.getValue())));
}
return new UrlEncodedFormEntity(formValues, contentType.getCharset());
} else {
// Handle files with unknown content type
if( obj instanceof File file ) {
return new FileEntity(file, contentType);
} else if( obj instanceof byte[] byteArray ) {
return new ByteArrayEntity(byteArray, contentType);
}
throw new OpenApiRequestException("Serialization for content type '" + contentType + "' not supported");
}
}

/**
* Build full URL by concatenating base URL, the given sub path and query parameters.
*
Expand Down Expand Up @@ -560,7 +492,7 @@ public <T> T invokeAPI(
if( body != null || !formParams.isEmpty() ) {
if( isBodyAllowed(Method.valueOf(method)) ) {
// Add entity if we have content and a valid method
builder.setEntity(serialize(body, formParams, contentTypeObj));
builder.setEntity(serialize(body, formParams, contentTypeObj, headerParams.get("Content-Encoding")));
} else {
throw new OpenApiRequestException("method " + method + " does not support a request body");
}
Expand All @@ -578,4 +510,112 @@ public <T> T invokeAPI(
throw new OpenApiRequestException(e);
}
}

/**
* Serialize the given Java object into string according the given Content-Type (only JSON is supported for now).
*
* @param body
* Object
* @param contentType
* Content type
* @param formParams
* Form parameters
* @return Object
* @throws OpenApiRequestException
* API exception
*/
@Nonnull
private HttpEntity serialize(
@Nullable final Object body,
@Nonnull final Map<String, Object> formParams,
@Nonnull final ContentType contentType,
@Nonnull final String contentEncoding )
throws OpenApiRequestException
{
final String mimeType = contentType.getMimeType();
if( isJsonMime(mimeType) ) {
return serializeJson(body, contentType, contentEncoding);
} else if( mimeType.equals(ContentType.MULTIPART_FORM_DATA.getMimeType()) ) {
return serializeMultipart(formParams, contentType);
} else if( mimeType.equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) ) {
return serializeFormUrlEncoded(formParams, contentType);
} else if( body instanceof File file ) {
return new FileEntity(file, contentType);
} else if( body instanceof byte[] byteArray ) {
return new ByteArrayEntity(byteArray, contentType);
}
throw new OpenApiRequestException("Serialization for content type '" + contentType + "' not supported");
}

@Nonnull
private HttpEntity serializeJson(
@Nullable final Object body,
@Nonnull final ContentType contentType,
@Nonnull final String contentEncoding )
throws OpenApiRequestException
{
if( "gzip".equals(contentEncoding) ) {
val outputStream = new ByteArrayOutputStream();
try( val gzip = new GZIPOutputStream(outputStream) ) {
gzip.write(objectMapper.writeValueAsBytes(body));
}
catch( IOException e ) {
throw new OpenApiRequestException(e);
}
return new ByteArrayEntity(outputStream.toByteArray(), contentType.withCharset(StandardCharsets.UTF_8));
}
try {
return new StringEntity(
objectMapper.writeValueAsString(body),
contentType.withCharset(StandardCharsets.UTF_8));
}
catch( JsonProcessingException e ) {
throw new OpenApiRequestException(e);
}
}

@Nonnull
private
HttpEntity
serializeMultipart( @Nonnull final Map<String, Object> formParams, @Nonnull final ContentType contentType )
{
final MultipartEntityBuilder builder = MultipartEntityBuilder.create();
for( final Entry<String, Object> entry : formParams.entrySet() ) {
final Object value = entry.getValue();
if( value instanceof File file ) {
builder.addBinaryBody(entry.getKey(), file);
} else if( value instanceof byte[] byteArray ) {
builder.addBinaryBody(entry.getKey(), byteArray);
} else {
addMultipartTextField(builder, entry, contentType);
}
}
return builder.build();
}

private void addMultipartTextField(
@Nonnull final MultipartEntityBuilder builder,
@Nonnull final Entry<String, Object> entry,
@Nonnull final ContentType contentType )
{
final Charset charset = contentType.getCharset();
if( charset != null ) {
final ContentType textContentType = ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), charset);
builder.addTextBody(entry.getKey(), parameterToString(entry.getValue()), textContentType);
} else {
builder.addTextBody(entry.getKey(), parameterToString(entry.getValue()));
}
}

@Nonnull
private
HttpEntity
serializeFormUrlEncoded( @Nonnull final Map<String, Object> formParams, @Nonnull final ContentType contentType )
{
final List<NameValuePair> formValues = new ArrayList<>();
for( final Entry<String, Object> entry : formParams.entrySet() ) {
formValues.add(new BasicNameValuePair(entry.getKey(), parameterToString(entry.getValue())));
}
return new UrlEncodedFormEntity(formValues, contentType.getCharset());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
Expand Down Expand Up @@ -30,6 +32,7 @@ class ApacheApiClientResponseHandlingTest
{
private static final String TEST_PATH = "/test";
private static final String TEST_RESPONSE_BODY = "{\"message\": \"success\"}";
private static final String TEST_POST_PATH = "/test-post";

@Test
void testResponseMetadataListener( final WireMockRuntimeInfo wmInfo )
Expand Down Expand Up @@ -84,6 +87,22 @@ void testCaseInsensitiveHeaderLookup( final WireMockRuntimeInfo wmInfo )
assertThat(headers.get("X-CUSTOM-HEADER")).contains("some-value");
}

@Test
void testGzipEncodedPayload( final WireMockRuntimeInfo wmInfo )
{
stubFor(post(urlEqualTo(TEST_POST_PATH)).willReturn(aResponse().withStatus(200).withBody(TEST_RESPONSE_BODY)));

final ApiClient apiClient = ApiClient.create().withBasePath(wmInfo.getHttpBaseUrl());

final TestPostApi api = new TestPostApi(apiClient);
final TestResponse result = api.executeGzipRequest();

assertThat(result).isNotNull();
assertThat(result.getMessage()).isEqualTo("success");

verify(1, postRequestedFor(urlEqualTo(TEST_POST_PATH)));
}

private static class TestApi extends BaseApi
{
private final String path;
Expand Down Expand Up @@ -113,7 +132,7 @@ TestResponse executeRequest()
final String[] localVarContentTypes = {};
final String localVarContentType = ApiClient.selectHeaderContentType(localVarContentTypes);

final TypeReference<TestResponse> localVarReturnType = new TypeReference<TestResponse>()
final TypeReference<TestResponse> localVarReturnType = new TypeReference<>()
{
};

Expand All @@ -133,6 +152,59 @@ TestResponse executeRequest()
}
}

private static class TestPostApi extends BaseApi
{
private final String path;

TestPostApi( final ApiClient apiClient )
{
this(apiClient, TEST_POST_PATH);
}

TestPostApi( final ApiClient apiClient, final String path )
{
super(apiClient);
this.path = path;
}

TestResponse executeGzipRequest()
throws OpenApiRequestException
{
final TestResponse requestBody = new TestResponse();
requestBody.setMessage("test payload");

final List<Pair> localVarQueryParams = new ArrayList<>();
final List<Pair> localVarCollectionQueryParams = new ArrayList<>();
final Map<String, String> localVarHeaderParams = new HashMap<>();
localVarHeaderParams.put("Content-Encoding", "gzip");
final Map<String, Object> localVarFormParams = new HashMap<>();

final String[] localVarAccepts = { "application/json" };
final String localVarAccept = ApiClient.selectHeaderAccept(localVarAccepts);

final String[] localVarContentTypes = { "application/json" };
final String localVarContentType = ApiClient.selectHeaderContentType(localVarContentTypes);

final TypeReference<TestResponse> localVarReturnType = new TypeReference<>()
{
};

return apiClient
.invokeAPI(
path,
"POST",
localVarQueryParams,
localVarCollectionQueryParams,
null,
requestBody,
localVarHeaderParams,
localVarFormParams,
localVarAccept,
localVarContentType,
localVarReturnType);
}
}

@Data
private static class TestResponse
{
Expand Down