From e95aac789d4aab24c244612b3f6be9579b47a81b Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 4 Mar 2026 10:56:04 +0100 Subject: [PATCH 01/10] feat: [OpenAPI] GZIP encoding --- .../openapi/apache/apiclient/ApiClient.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 9c41962c0..8f5170190 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -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; @@ -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; @@ -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. @@ -560,7 +563,11 @@ public 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)); + if( "gzip".equals(headerParams.get("Content-Encoding")) ) { + builder.setEntity(serializeGzip(body, contentTypeObj)); + } else { + builder.setEntity(serialize(body, formParams, contentTypeObj)); + } } else { throw new OpenApiRequestException("method " + method + " does not support a request body"); } @@ -578,4 +585,16 @@ public T invokeAPI( throw new OpenApiRequestException(e); } } + + private HttpEntity serializeGzip( Object body, ContentType contentType ) + { + 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)); + } } From 660fd54b34e6f6e623dcb5bc61634fb6194c2b0d Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 4 Mar 2026 11:07:15 +0100 Subject: [PATCH 02/10] PMD --- .../cloud/sdk/services/openapi/apache/apiclient/ApiClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 8f5170190..587ee9899 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -586,7 +586,7 @@ public T invokeAPI( } } - private HttpEntity serializeGzip( Object body, ContentType contentType ) + private HttpEntity serializeGzip( final Object body, final ContentType contentType ) { val outputStream = new ByteArrayOutputStream(); try( val gzip = new GZIPOutputStream(outputStream) ) { From d197730295244ce6470eb1953e847720d006f278 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 4 Mar 2026 11:14:09 +0100 Subject: [PATCH 03/10] Better if --- .../services/openapi/apache/apiclient/ApiClient.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 587ee9899..a90826000 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -563,11 +563,11 @@ public T invokeAPI( if( body != null || !formParams.isEmpty() ) { if( isBodyAllowed(Method.valueOf(method)) ) { // Add entity if we have content and a valid method - if( "gzip".equals(headerParams.get("Content-Encoding")) ) { - builder.setEntity(serializeGzip(body, contentTypeObj)); - } else { - builder.setEntity(serialize(body, formParams, contentTypeObj)); - } + builder + .setEntity( + "gzip".equals(headerParams.get("Content-Encoding")) + ? serializeGzip(body, contentTypeObj) + : serialize(body, formParams, contentTypeObj)); } else { throw new OpenApiRequestException("method " + method + " does not support a request body"); } From 38b8557438eba12de11196e237963b49ef1cace0 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Mar 2026 13:38:44 +0100 Subject: [PATCH 04/10] temporary formatting --- .../openapi/apache/apiclient/ApiClient.java | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index a90826000..7b340fbd2 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -368,18 +368,30 @@ private ContentType getContentType( @Nonnull final String headerValue ) private HttpEntity serialize( @Nullable final Object obj, @Nonnull final Map formParams, - @Nonnull final ContentType contentType ) + @Nonnull final ContentType contentType, + @Nonnull final Map headerParams ) 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); + if( "gzip".equals(headerParams.get("Content-Encoding")) ) { + val outputStream = new ByteArrayOutputStream(); + try( val gzip = new GZIPOutputStream(outputStream) ) { + gzip.write(objectMapper.writeValueAsBytes(obj)); + } + catch( IOException e ) { + throw new OpenApiRequestException(e); + } + return new ByteArrayEntity(outputStream.toByteArray(), contentType.withCharset(StandardCharsets.UTF_8)); + } else { + 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(); @@ -563,11 +575,7 @@ public T invokeAPI( if( body != null || !formParams.isEmpty() ) { if( isBodyAllowed(Method.valueOf(method)) ) { // Add entity if we have content and a valid method - builder - .setEntity( - "gzip".equals(headerParams.get("Content-Encoding")) - ? serializeGzip(body, contentTypeObj) - : serialize(body, formParams, contentTypeObj)); + builder.setEntity(serialize(body, formParams, contentTypeObj, headerParams)); } else { throw new OpenApiRequestException("method " + method + " does not support a request body"); } @@ -585,16 +593,4 @@ public T invokeAPI( throw new OpenApiRequestException(e); } } - - private HttpEntity serializeGzip( final Object body, final ContentType contentType ) - { - 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)); - } } From 744d2c68802acd3e25720fe1315c4d7338168e54 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Mar 2026 13:46:13 +0100 Subject: [PATCH 05/10] finito formatting --- .../openapi/apache/apiclient/ApiClient.java | 193 ++++++++++-------- 1 file changed, 109 insertions(+), 84 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 7b340fbd2..00131da0d 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -351,89 +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 formParams, - @Nonnull final ContentType contentType, - @Nonnull final Map headerParams ) - throws OpenApiRequestException - { - final String mimeType = contentType.getMimeType(); - if( isJsonMime(mimeType) ) { - if( "gzip".equals(headerParams.get("Content-Encoding")) ) { - val outputStream = new ByteArrayOutputStream(); - try( val gzip = new GZIPOutputStream(outputStream) ) { - gzip.write(objectMapper.writeValueAsBytes(obj)); - } - catch( IOException e ) { - throw new OpenApiRequestException(e); - } - return new ByteArrayEntity(outputStream.toByteArray(), contentType.withCharset(StandardCharsets.UTF_8)); - } else { - 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 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 formValues = new ArrayList<>(); - for( final Entry 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. * @@ -575,7 +492,7 @@ public 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, headerParams)); + builder.setEntity(serialize(body, formParams, contentTypeObj, headerParams.get("Content-Encoding"))); } else { throw new OpenApiRequestException("method " + method + " does not support a request body"); } @@ -593,4 +510,112 @@ public 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 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 formParams, @Nonnull final ContentType contentType ) + { + final MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + for( final Entry 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 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 formParams, @Nonnull final ContentType contentType ) + { + final List formValues = new ArrayList<>(); + for( final Entry entry : formParams.entrySet() ) { + formValues.add(new BasicNameValuePair(entry.getKey(), parameterToString(entry.getValue()))); + } + return new UrlEncodedFormEntity(formValues, contentType.getCharset()); + } } From 1f53196afcbd57f4c28dc79dc4b625ce78c2a399 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Mar 2026 13:52:06 +0100 Subject: [PATCH 06/10] Added test --- .../ApacheApiClientResponseHandlingTest.java | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java index 5c64fdce1..b2dfece61 100644 --- a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java +++ b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java @@ -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; @@ -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 ) @@ -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; @@ -104,7 +123,7 @@ TestResponse executeRequest() { final List localVarQueryParams = new ArrayList<>(); final List localVarCollectionQueryParams = new ArrayList<>(); - final Map localVarHeaderParams = new HashMap<>(); + final Map localVarHeaderParams = Map.of("Content-Encoding", "gzip"); final Map localVarFormParams = new HashMap<>(); final String[] localVarAccepts = { "application/json" }; @@ -113,7 +132,7 @@ TestResponse executeRequest() final String[] localVarContentTypes = {}; final String localVarContentType = ApiClient.selectHeaderContentType(localVarContentTypes); - final TypeReference localVarReturnType = new TypeReference() + final TypeReference localVarReturnType = new TypeReference<>() { }; @@ -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 localVarQueryParams = new ArrayList<>(); + final List localVarCollectionQueryParams = new ArrayList<>(); + final Map localVarHeaderParams = new HashMap<>(); + localVarHeaderParams.put("Content-Encoding", "gzip"); + final Map 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 localVarReturnType = new TypeReference<>() + { + }; + + return apiClient + .invokeAPI( + path, + "POST", + localVarQueryParams, + localVarCollectionQueryParams, + null, + requestBody, + localVarHeaderParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarReturnType); + } + } + @Data private static class TestResponse { From 341e551735d8448a7fc4e3b851ce00362bd156e6 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Mar 2026 13:53:07 +0100 Subject: [PATCH 07/10] Added test --- .../apache/apiclient/ApacheApiClientResponseHandlingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java index b2dfece61..096069b8e 100644 --- a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java +++ b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java @@ -123,7 +123,7 @@ TestResponse executeRequest() { final List localVarQueryParams = new ArrayList<>(); final List localVarCollectionQueryParams = new ArrayList<>(); - final Map localVarHeaderParams = Map.of("Content-Encoding", "gzip"); + final Map localVarHeaderParams = new HashMap<>(); final Map localVarFormParams = new HashMap<>(); final String[] localVarAccepts = { "application/json" }; From f4d2f7b410be176dcb0d0d97d44f0f3899415d3e Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Mar 2026 13:53:34 +0100 Subject: [PATCH 08/10] format --- .../apache/apiclient/ApacheApiClientResponseHandlingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java index 096069b8e..5f3259d57 100644 --- a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java +++ b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java @@ -123,7 +123,7 @@ TestResponse executeRequest() { final List localVarQueryParams = new ArrayList<>(); final List localVarCollectionQueryParams = new ArrayList<>(); - final Map localVarHeaderParams = new HashMap<>(); + final Map localVarHeaderParams = new HashMap<>(); final Map localVarFormParams = new HashMap<>(); final String[] localVarAccepts = { "application/json" }; From 5a72fa072ba0ba369a6d36e0cb1c1ee514141b5a Mon Sep 17 00:00:00 2001 From: I538344 Date: Fri, 13 Mar 2026 09:13:13 +0100 Subject: [PATCH 09/10] nullable --- .../sdk/services/openapi/apache/apiclient/ApiClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 00131da0d..08c73c2c0 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -529,7 +529,7 @@ private HttpEntity serialize( @Nullable final Object body, @Nonnull final Map formParams, @Nonnull final ContentType contentType, - @Nonnull final String contentEncoding ) + @Nullable final String contentEncoding ) throws OpenApiRequestException { final String mimeType = contentType.getMimeType(); @@ -551,7 +551,7 @@ private HttpEntity serialize( private HttpEntity serializeJson( @Nullable final Object body, @Nonnull final ContentType contentType, - @Nonnull final String contentEncoding ) + @Nullable final String contentEncoding ) throws OpenApiRequestException { if( "gzip".equals(contentEncoding) ) { From e7ea5d55c639b3972b8936d214dfcc0fdef88b4c Mon Sep 17 00:00:00 2001 From: I538344 Date: Fri, 13 Mar 2026 11:26:02 +0100 Subject: [PATCH 10/10] Alex's review --- datamodel/openapi/openapi-core-apache/pom.xml | 12 ++++++++++- .../openapi/apache/apiclient/ApiClient.java | 21 ++++++++++++------- .../ApacheApiClientResponseHandlingTest.java | 17 +++++++++++++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/datamodel/openapi/openapi-core-apache/pom.xml b/datamodel/openapi/openapi-core-apache/pom.xml index da363410d..47287dea5 100644 --- a/datamodel/openapi/openapi-core-apache/pom.xml +++ b/datamodel/openapi/openapi-core-apache/pom.xml @@ -94,5 +94,15 @@ junit-jupiter-params test + + org.mockito + mockito-core + test + + + io.vavr + vavr + test + - \ No newline at end of file + diff --git a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java index 08c73c2c0..ac2b4fad9 100644 --- a/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java +++ b/datamodel/openapi/openapi-core-apache/src/main/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApiClient.java @@ -14,6 +14,7 @@ import static com.sap.cloud.sdk.services.openapi.apache.apiclient.DefaultApiResponseHandler.isJsonMime; import static lombok.AccessLevel.PRIVATE; +import static org.apache.hc.core5.http.HttpHeaders.CONTENT_ENCODING; import java.io.ByteArrayOutputStream; import java.io.File; @@ -492,7 +493,7 @@ public 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, headerParams.get("Content-Encoding"))); + builder.setEntity(serialize(body, formParams, contentTypeObj, headerParams)); } else { throw new OpenApiRequestException("method " + method + " does not support a request body"); } @@ -520,6 +521,8 @@ public T invokeAPI( * Content type * @param formParams * Form parameters + * @param headerParams + * Header parameters, used to check content encoding for JSON serialization * @return Object * @throws OpenApiRequestException * API exception @@ -529,12 +532,12 @@ private HttpEntity serialize( @Nullable final Object body, @Nonnull final Map formParams, @Nonnull final ContentType contentType, - @Nullable final String contentEncoding ) + @Nonnull final Map headerParams ) throws OpenApiRequestException { final String mimeType = contentType.getMimeType(); if( isJsonMime(mimeType) ) { - return serializeJson(body, contentType, contentEncoding); + return serializeJson(body, contentType, headerParams); } else if( mimeType.equals(ContentType.MULTIPART_FORM_DATA.getMimeType()) ) { return serializeMultipart(formParams, contentType); } else if( mimeType.equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType()) ) { @@ -551,18 +554,22 @@ private HttpEntity serialize( private HttpEntity serializeJson( @Nullable final Object body, @Nonnull final ContentType contentType, - @Nullable final String contentEncoding ) + @Nonnull final Map headerParams ) throws OpenApiRequestException { - if( "gzip".equals(contentEncoding) ) { + if( "gzip".equalsIgnoreCase(headerParams.get(CONTENT_ENCODING)) + || "gzip".equalsIgnoreCase(headerParams.get(CONTENT_ENCODING.toLowerCase())) ) { val outputStream = new ByteArrayOutputStream(); try( val gzip = new GZIPOutputStream(outputStream) ) { gzip.write(objectMapper.writeValueAsBytes(body)); } catch( IOException e ) { - throw new OpenApiRequestException(e); + throw new OpenApiRequestException("Failed to GZIP compress request body", e); } - return new ByteArrayEntity(outputStream.toByteArray(), contentType.withCharset(StandardCharsets.UTF_8)); + return new ByteArrayEntity( + outputStream.toByteArray(), + contentType.withCharset(StandardCharsets.UTF_8), + "gzip"); } try { return new StringEntity( diff --git a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java index 5f3259d57..0a3e39464 100644 --- a/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java +++ b/datamodel/openapi/openapi-core-apache/src/test/java/com/sap/cloud/sdk/services/openapi/apache/apiclient/ApacheApiClientResponseHandlingTest.java @@ -10,22 +10,30 @@ import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.Assertions.assertThat; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPInputStream; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.protocol.HttpContext; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; import com.sap.cloud.sdk.services.openapi.apache.core.OpenApiRequestException; import com.sap.cloud.sdk.services.openapi.apache.core.OpenApiResponse; +import io.vavr.control.Try; import lombok.Data; +import lombok.SneakyThrows; @WireMockTest class ApacheApiClientResponseHandlingTest @@ -88,14 +96,19 @@ void testCaseInsensitiveHeaderLookup( final WireMockRuntimeInfo wmInfo ) } @Test + @SneakyThrows 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 CloseableHttpClient client = Mockito.spy((CloseableHttpClient) ApacheHttpClient5Accessor.getHttpClient()); + final ApiClient apiClient = ApiClient.fromHttpClient(client).withBasePath(wmInfo.getHttpBaseUrl()); final TestPostApi api = new TestPostApi(apiClient); final TestResponse result = api.executeGzipRequest(); + Mockito.verify(client, Mockito.times(1)).execute(Mockito.argThat(request -> { + final byte[] c = Try.of(() -> new GZIPInputStream(request.getEntity().getContent()).readAllBytes()).get(); + return new String(c, StandardCharsets.UTF_8).contains("test payload"); + }), Mockito.any(HttpContext.class), Mockito.any()); assertThat(result).isNotNull(); assertThat(result.getMessage()).isEqualTo("success");