8000 chore: [OpenAPI] Reduce Spring dependency by newtork · Pull Request #754 · SAP/cloud-sdk-java · GitHub
[go: up one dir, main page]

Skip to content

chore: [OpenAPI] Reduce Spring dependency #754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
< 8000 div >
Next Next commit
Initial client change
  • Loading branch information
a-d committed Mar 14, 2025
commit 817e0ff33484b8f268c27cf2708f9c71261b17d8
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sap.cloud.sdk.services.openapi.apiclient;

import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
Expand All @@ -12,6 +13,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.TimeZone;

import javax.annotation.Nonnull;
Expand All @@ -38,6 +40,7 @@

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
Expand Down Expand Up @@ -476,7 +479,7 @@ public String collectionPathParameterToString(
* @return a Map containing the String value(s) of the input parameter
*/
@Nonnull
public MultiValueMap<String, String> parameterToMultiValueMap(
public Map<String, List<String>> parameterToMultiValueMap(
@Nullable CollectionFormat collectionFormat,
@Nullable final String name,
@Nullable final Object value )
Expand Down Expand Up @@ -622,6 +625,20 @@ public MediaType selectHeaderContentType( @Nonnull final String[] contentTypes )
return MediaType.parseMediaType(contentTypes[0]);
}

/**
* Select the Content-Type header's value from the given array: if JSON exists in the given array, use it; otherwise
* use the first one of the array.
*
* @param contentTypes
* The Content-Type array to select from
* @return MediaType The Content-Type header to use. If the given array is empty, JSON will be used.
*/
@Nonnull
public String getHeaderContentType( @Nonnull final String[] contentTypes )
{
return selectHeaderContentType(contentTypes).toString();
}

/**
* Select the body to use for the request
*
Expand All @@ -632,14 +649,19 @@ public MediaType selectHeaderContentType( @Nonnull final String[] contentTypes )
* @param contentType
* the content type of the request
* @return Object the selected body
* @deprecated Avoid using Spring dependent classes.
*/
private
Object
selectBody( final Object obj, final MultiValueMap<String, Object> formParams, final MediaType contentType )
@Deprecated
@Nullable
private Object selectBody(
@Nullable final Object obj,
@Nullable final Map<String, List<Object>> formParams,
@Nullable final Object contentType )
{
final MediaType mediaType = contentType == null ? null : MediaType.valueOf(contentType.toString());
final boolean isForm =
MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)
|| MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType);
MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)
|| MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType);
return isForm ? formParams : obj;
}

Expand All @@ -651,7 +673,7 @@ public MediaType selectHeaderContentType( @Nonnull final String[] contentTypes )
* @param path
* The sub-path of the HTTP URL
* @param method
* The request method
* The request method. `toString()` resolves to e.g. `"GET"` or `"POST"`
* @param queryParams
* The query parameters
* @param body
Expand All @@ -661,29 +683,30 @@ public MediaType selectHeaderContentType( @Nonnull final String[] contentTypes )
* @param formParams
* The form parameters
* @param accept
* The request's Accept header
* The request's Accept header. `toString()` on list items resolve to e.g. `"application/json"`
* @param contentType
* The request's Content-Type header
* The request's Content-Type header. `toString()` resolves to e.g. `"application/json, application/xml"`
* @param authNames
* The authentications to apply
* The authentications to apply. Not used in this implementation.
* @param returnType
* The return type into which to deserialize the response
* The return type into which to deserialize the response, e.g. `FooBar.class`
* @return The response body in chosen type
* @throws OpenApiRequestException
* Thrown in case an exception occurs during invocation of the REST API
*/
@SuppressWarnings( "unchecked" )
@Nullable
public <T> T invokeAPI(
Copy link
Contributor Author
@newtork newtork Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Comment)

Weaken API contract to allow non-Spring arguments:

image

Update in usage:

image

First step:

  • Remove Spring requirement from public API.
    • No breaking change with existing generated code.
    • Code quality must still be improved though: Deprecation, Replacement, Delegation.

Next step:

  • Improve code by offering an implementation for invokeAPI that does not require transitive Spring dependencies. While keeping legacy consumers unaffected on API contract.

@Nonnull final String path,
@Nonnull final HttpMethod method,
@Nullable final MultiValueMap<String, String> queryParams,
@Nonnull final Object method,
@Nullable final Map<String, List<String>> queryParams,
@Nullable final Object body,
@Nullable final HttpHeaders headerParams,
@Nullable final MultiValueMap<String, Object> formParams,
@Nullable final List<MediaType> accept,
@Nullable final MediaType contentType,
@Nullable final Map<String, List<String>> headerParams,
@Nullable final Map<String, List<Object>> formParams,
@Nullable final List<?> accept,
@Nullable final Object contentType,
@Nullable final String[] authNames,
@Nonnull final ParameterizedTypeReference<T> returnType )
@Nonnull final Object returnType )
throws OpenApiRequestException
{
// auth headers are added automatically by the SDK
Expand All @@ -698,7 +721,7 @@ public <T> T invokeAPI(
values.replaceAll(queryParam -> UriUtils.encodeQueryParam(queryParam, "utf8"));
}
}
builder.queryParams(queryParams);
builder.queryParams(MultiValueMap.fromMultiValue(queryParams));
}

final URI uri;
Expand All @@ -709,20 +732,37 @@ public <T> T invokeAPI(
throw new OpenApiRequestException("Could not build URL: " + builder.toUriString(), ex);
}

final BodyBuilder requestBuilder = RequestEntity.method(method, uri);
final BodyBuilder requestBuilder = RequestEntity.method(HttpMethod.valueOf(method.toString()), uri);
if( accept != null ) {
requestBuilder.accept(accept.toArray(new MediaType[0]));
requestBuilder
.accept(accept.stream().map(Object::toString).map(MediaType::valueOf).toArray(MediaType[]::new));
}
if( contentType != null ) {
requestBuilder.contentType(contentType);
requestBuilder.contentType(MediaType.valueOf(contentType.toString()));
}

addHeadersToRequest(headerParams, requestBuilder);
addHeadersToRequest(defaultHeaders, requestBuilder);

@SuppressWarnings( "DataFlowIssue" )
final RequestEntity<Object> requestEntity = requestBuilder.body(selectBody(body, formParams, contentType));

final ResponseEntity<T> responseEntity = restTemplate.exchange(requestEntity, returnType);
final ResponseEntity<T> responseEntity;
if( returnType instanceof final ParameterizedTypeReference<?> returnTypeRef ) {
responseEntity = (ResponseEntity<T>) restTemplate.exchange(requestEntity, returnTypeRef);
} else if( returnType instanceof final TypeReference<?> returnTypeRefJackson ) {
final ParameterizedTypeReference<T> returnTypeRefSpring =
ParameterizedTypeReference.forType(returnTypeRefJackson.getType());
responseEntity = restTemplate.exchange(requestEntity, returnTypeRefSpring);
} else if( returnType instanceof final Class<?> returnTypeClass ) {
responseEntity = restTemplate.exchange(requestEntity, (Class<T>) returnTypeClass);
} else if( returnType instanceof final Type returnTypeType ) {
final ParameterizedTypeReference<T> returnTypeRefSpring =
ParameterizedTypeReference.forType(returnTypeType);
responseEntity = restTemplate.exchange(requestEntity, returnTypeRefSpring);
} else {
throw new IllegalArgumentException("Unsupported return type argument: " + returnType);
}

statusCode = responseEntity.getStatusCode().value();
responseHeaders = responseEntity.getHeaders();
Expand All @@ -746,7 +786,9 @@ public <T> T invokeAPI(
* @param requestBuilder
* The current request
*/
private void addHeadersToRequest( @Nullable final HttpHeaders headers, final BodyBuilder requestBuilder )
private
void
addHeadersToRequest( @Nullable final Map<String, List<String>> headers, final BodyBuilder requestBuilder )
{
if( headers != null ) {
for( final Entry<String, List<String>> entry : headers.entrySet() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private static void httpRequest( TlsUpgrade toggle, String url )

assertThat(
apiClient
.invokeAPI(
.<String> invokeAPI(
"/apiEndpoint",
HttpMethod.GET,
null,
Expand Down
Loading
0