SAP / cloud-sdk

The SAP Cloud SDK documentation and support repository.
https://sap.github.io/cloud-sdk/
Apache License 2.0
45 stars 43 forks source link

Invalid document data upload using AttachmentContent #175

Closed rafaelferrari closed 3 years ago

rafaelferrari commented 3 years ago

Issue Description

Hi colleagues,

Our team is working with the attachments APIs (especifically, the DefaultAPICVATTACHMENTSRVService service) and we found an error with the files that were uploaded using the SDK. The operation runs fine and the document is uploaded to S/4 with the correct linked object key/type and metadata (file name, mime type); however, the actual data for the document is not valid.

Through a binary comparison between the original file and the one retrieved from S/4, I've noticed that data for the uploaded file is actually readable in a text editor and it contains a JSON representation of the actual file. E.g.:

{
  "LinkedSAPObjectKey":"123456789",
  "FileSize":"39618",
  "FileName":"testfile.jpg",
  "MimeType":"image/jpeg",
  "Content":"base64encodeddatahere",
  "BusinessObjectType":"EXAMPLE"
}

Here is how our code looks like when creating the command (using the generated builders, we also tried to use the setter methods but we got the same result):

byte[] attachmentData = document.getDataInBytes();
AttachmentContent attachmentContent = AttachmentContent.builder()
  .attachmentContent(attachmentData)
  .fileSize(String.valueOf(attachmentData.length))
  .fileName(document.getName())
  .mimeType(document.getMimeType())
  .linkedSAPObjectKey(document.getKey())
  .businessObjectType("EXAMPLE")
  .build();

This attachmentContent variable is then provided when we call the createAttachmentContent() method on DefaultAPICVATTACHMENTSRVService, but somewhere along the way it seems like the content is being replaced with the JSON containing both the data and the metadata.

The command's run() method looks like this:

private Map<String, String> getUploadHeaders() {
    Map<String, String> headers = new HashMap<>();
    headers.put("Slug", attachmentContent.getFileName());
    headers.put("Content-Type", attachmentContent.getMimeType());
    headers.put("BusinessObjectTypeName", "EXAMPLE");
    headers.put("LinkedSAPObjectKey", attachmentContent.getLinkedSAPObjectKey());
    return headers;
}
...
defaultAttachmentsService
  .createAttachmentContent(attachmentContent)
  .withCustomHttpHeaders(getUploadHeaders()).onRequestAndImplicitRequests()
  .execute(getConfigContext());

We are currently unable to perform this operation via API (e.g. Postman) but we're working on that. Currently we don't have a working example of how the body and headers should look like in the API request, so if you could share some example request with us we'd appreciate it.

Right now we are unsure if this is caused by something we are missing from our code or if there is a bug in this service somewhere. Could you please assist us with this issue?

Impact / Priority

Affected development phase: Development

Impact: Blocked

Timeline: Go-Live is in 6 weeks.

Error Message

Maven Dependency Tree

--- maven-dependency-plugin:3.1.2:tree (default-cli) @ backend ---
com.sap.cloudscame:backend:war:3.1.0
+- com.sap.cloud:neo-java-web-sdk:zip:3.81.16:provided
+- com.fasterxml.jackson.core:jackson-core:jar:2.12.1:compile
+- com.fasterxml.jackson.core:jackson-annotations:jar:2.12.1:compile
+- com.sap.cloud.s4hana.cloudplatform:scp-neo:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:auditlog-scp-neo:jar:2.28.0:compile
|  |  +- com.sap.cloud.s4hana.cloudplatform:core:jar:2.28.0:compile
|  |  \- com.sap.cloud.s4hana.cloudplatform:auditlog:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:caching:jar:2.28.0:compile
|  |  +- com.sap.cloud.s4hana.cloudplatform:security:jar:2.28.0:compile
|  |  \- com.sap.cloud.s4hana.cloudplatform:tenant:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:core-scp-neo:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:connectivity-scp-neo:jar:2.28.0:compile
|  |  +- com.sap.cloud.s4hana.cloudplatform:connectivity:jar:2.28.0:compile
|  |  |  \- com.sap.cloud.s4hana.quality:common:jar:2.28.0:compile
|  |  +- com.sap.cloud.s4hana.frameworks:hystrix:jar:2.28.0:compile
|  |  |  \- com.netflix.archaius:archaius-core:jar:0.7.6:compile
|  |  \- com.netflix.hystrix:hystrix-core:jar:1.5.18:compile
|  |     +- io.reactivex:rxjava:jar:1.3.8:compile
|  |     \- org.hdrhistogram:HdrHistogram:jar:2.1.9:compile
|  +- com.sap.cloud.s4hana.cloudplatform:security-scp-neo:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:servlet:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:tenant-scp-neo:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.cloudplatform:metering-scp-neo:jar:2.28.0:compile
|  \- com.sap.cloud.s4hana.frameworks:hystrix-scp-neo:jar:2.28.0:compile
|     \- com.sap.cloud.s4hana.cloudplatform:concurrency-scp-neo:jar:2.28.0:compile
+- com.sap.cloud.s4hana.datamodel:odata-generator:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana.datamodel:odata-core:jar:2.28.0:compile
|  |  +- com.sap.cloud.s4hana:core:jar:2.28.0:compile
|  |  +- org.jsoup:jsoup:jar:1.11.3:compile
|  |  +- joda-time:joda-time:jar:2.10.8:compile
|  |  \- io.vavr:vavr:jar:0.9.2:compile
|  |     \- io.vavr:vavr-match:jar:0.9.2:compile
|  +- com.sap.cloud.s4hana:fluent-result:jar:2.28.0:compile
|  +- com.sap.cloud.s4hana:connectivity:jar:2.28.0:compile
|  +- com.sap.cloud.servicesdk:odata-v2-lib:jar:1.35.2:compile
|  |  \- com.sap.cloud.servicesdk:developer_license:jar:1.35.2:compile
|  +- com.sap.cloud.servicesdk:odatav2-connectivity:jar:1.35.2:compile
|  |  +- com.sap.cloud.servicesdk:jacksonutil:jar:1.35.2:compile
|  |  +- org.json:json:jar:20090211:compile
|  |  \- com.sap.cloud.servicesdk.prov:api:jar:1.35.2:compile
|  |     \- com.sap.cds:cds4j-api:jar:1.3.0:compile
|  +- org.slf4j:jcl-over-slf4j:jar:1.7.30:runtime
|  +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
|  +- com.google.guava:guava:jar:27.0.1-jre:compile
|  |  +- com.google.guava:failureaccess:jar:1.0.1:compile
|  |  +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
|  |  +- org.checkerframework:checker-qual:jar:2.5.2:compile
|  |  +- com.google.errorprone:error_prone_annotations:jar:2.2.0:compile
|  |  +- com.google.j2objc:j2objc-annotations:jar:1.1:compile
|  |  \- org.codehaus.mojo:animal-sniffer-annotations:jar:1.17:compile
|  +- com.fasterxml.jackson.core:jackson-databind:jar:2.12.1:compile
|  +- org.apache.commons:commons-lang3:jar:3.8.1:compile
|  +- commons-configuration:commons-configuration:jar:1.7:compile
|  |  +- commons-lang:commons-lang:jar:2.6:compile
|  |  \- commons-digester:commons-digester:jar:1.8.1:compile
|  +- commons-io:commons-io:jar:2.6:compile
|  +- org.apache.commons:commons-text:jar:1.4:compile
|  \- com.sun.codemodel:codemodel:jar:2.6:compile
+- commons-beanutils:commons-beanutils:jar:1.9.4:compile
|  +- commons-logging:commons-logging:jar:1.2:compile
|  \- commons-collections:commons-collections:jar:3.2.2:compile
+- com.mikesamuel:json-sanitizer:jar:1.2.2:compile
+- com.querydsl:querydsl-apt:jar:4.2.2:provided
|  \- com.querydsl:querydsl-codegen:jar:4.2.2:provided
|     +- com.mysema.codegen:codegen:jar:0.6.8:provided
|     |  \- org.eclipse.jdt.core.compiler:ecj:jar:4.3.1:provided
|     \- org.reflections:reflections:jar:0.9.9:provided
|        +- org.javassist:javassist:jar:3.18.2-GA:provided
|        \- com.google.code.findbugs:annotations:jar:2.0.1:provided
+- com.querydsl:querydsl-jpa:jar:4.2.2:compile
|  \- com.querydsl:querydsl-core:jar:4.2.2:compile
|     +- com.mysema.commons:mysema-commons-lang:jar:0.2.4:compile
|     \- com.infradna.tool:bridge-method-annotation:jar:1.13:compile
+- com.sap.cloud:neo-java-web-api:jar:3.81.16:provided
|  +- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.3.1:provided
|  +- javax.mail:javax.mail-api:jar:1.6.2:provided
|  +- org.apache.chemistry.opencmis:chemistry-opencmis-client-api:jar:1.0.0:provided
|  +- javax.websocket:javax.websocket-api:jar:1.1:provided
|  +- javax.el:javax.el-api:jar:3.0.0:provided
|  +- org.apache.chemistry.opencmis:chemistry-opencmis-commons-api:jar:1.0.0:provided
|  +- org.glassfish:javax.annotation:jar:3.1-b41:provided
|  \- javax.servlet:javax.servlet-api:jar:4.0.1:provided
+- org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.7.2:compile
|  +- org.eclipse.persistence:javax.persistence:jar:2.2.0:compile
|  +- org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.7.2:compile
|  +- org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.7.2:compile
|  +- org.glassfish:javax.json:jar:1.0.4:compile
|  +- org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.7.2:compile
|  \- org.eclipse.persistence:org.eclipse.persistence.core:jar:2.7.2:compile
+- com.google.code.gson:gson:jar:2.8.6:compile
+- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.18.RELEASE:compile
|  +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.18.RELEASE:compile
|  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.18.RELEASE:compile
|  |  \- com.zaxxer:HikariCP:jar:3.2.0:compile
|  +- javax.transaction:javax.transaction-api:jar:1.3:compile
|  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
|  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
|  \- org.springframework.data:spring-data-jpa:jar:2.1.21.RELEASE:compile
|     \- org.springframework.data:spring-data-commons:jar:2.1.21.RELEASE:compile
+- org.springframework.boot:spring-boot-starter-security:jar:2.1.18.RELEASE:compile
|  +- org.springframework.boot:spring-boot-starter:jar:2.1.18.RELEASE:compile
|  |  \- javax.annotation:javax.annotation-api:jar:1.3.2:compile
|  +- org.springframework.security:spring-security-config:jar:5.1.13.RELEASE:compile
|  \- org.springframework.security:spring-security-web:jar:5.1.13.RELEASE:compile
+- org.yaml:snakeyaml:jar:1.26:compile
+- org.springframework:spring-web:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-core:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-jdbc:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-orm:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-tx:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-aspects:jar:5.1.19.RELEASE:compile
|  \- org.aspectj:aspectjweaver:jar:1.9.6:compile
+- org.springframework:spring-aop:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-expression:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-beans:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-jcl:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-webmvc:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-test:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-context:jar:5.1.19.RELEASE:compile
+- org.springframework:spring-context-support:jar:5.1.19.RELEASE:compile
+- org.springframework.boot:spring-boot-starter-web:jar:2.1.18.RELEASE:compile
|  +- org.springframework.boot:spring-boot-starter-json:jar:2.1.18.RELEASE:compile
|  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.10:compile
|  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.10:compile
|  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.10:compile
|  \- org.hibernate.validator:hibernate-validator:jar:6.1.5.Final:compile
|     +- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile
|     +- org.jboss.logging:jboss-logging:jar:3.3.3.Final:compile
|     \- com.fasterxml:classmate:jar:1.4.0:compile
+- org.springframework.boot:spring-boot-starter-logging:jar:2.1.18.RELEASE:compile
|  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.2:compile
|  |  \- org.apache.logging.log4j:log4j-api:jar:2.13.2:compile
|  \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
+- org.springframework.boot:spring-boot-starter-test:jar:2.1.18.RELEASE:test
|  +- org.springframework.boot:spring-boot-test:jar:2.1.18.RELEASE:test
|  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.18.RELEASE:test
|  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
|  |  \- net.minidev:json-smart:jar:2.3:test
|  |     \- net.minidev:accessors-smart:jar:1.2:test
|  |        \- org.ow2.asm:asm:jar:5.0.4:test
|  +- junit:junit:jar:4.13.1:test
|  +- org.assertj:assertj-core:jar:3.11.1:test
|  +- org.mockito:mockito-core:jar:2.23.4:test
|  |  +- net.bytebuddy:byte-buddy:jar:1.9.16:test
|  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.16:test
|  |  \- org.objenesis:objenesis:jar:2.6:test
|  +- org.hamcrest:hamcrest-core:jar:1.3:test
|  +- org.hamcrest:hamcrest-library:jar:1.3:test
|  +- org.skyscreamer:jsonassert:jar:1.5.0:test
|  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
|  \- org.xmlunit:xmlunit-core:jar:2.6.4:test
+- org.springframework.security:spring-security-test:jar:5.1.13.RELEASE:test
|  \- org.springframework.security:spring-security-core:jar:5.1.13.RELEASE:compile
+- org.liquibase:liquibase-core:jar:3.6.3:compile
+- org.apache.poi:poi:jar:4.1.1:compile
|  +- commons-codec:commons-codec:jar:1.14:compile
|  +- org.apache.commons:commons-collections4:jar:4.4:compile
|  \- org.apache.commons:commons-math3:jar:3.6.1:compile
+- org.apache.poi:poi-ooxml:jar:4.1.1:compile
|  +- org.apache.poi:poi-ooxml-schemas:jar:4.1.1:compile
|  |  \- org.apache.xmlbeans:xmlbeans:jar:3.1.0:compile
|  +- org.apache.commons:commons-compress:jar:1.19:compile
|  \- com.github.virtuald:curvesapi:jar:1.06:compile
+- com.sun.mail:javax.mail:jar:1.6.1:compile
|  \- javax.activation:activation:jar:1.1:compile
+- org.subethamail:subethasmtp:jar:3.1.7:test
|  \- javax.mail:mail:jar:1.4.4:test
+- org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.1.18.RELEASE:compile
|  +- org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
|  |  \- org.thymeleaf:thymeleaf:jar:3.0.11.RELEASE:compile
|  |     +- org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
|  |     \- org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
|  \- org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile
+- org.projectlombok:lombok:jar:1.18.16:provided
+- javax.inject:javax.inject:jar:1:provided
+- com.sap.ibsosle:dppservice:jar:1.0.2:compile
+- com.sap.core.service.auditlog:com.sap.core.service.auditlog.api:jar:0.6.48:provided
+- com.sap.core.service.auditlog:com.sap.core.service.auditlog.impl:jar:0.6.48:provided
+- org.slf4j:slf4j-api:jar:1.7.30:provided
+- ch.qos.logback:logback-classic:jar:1.2.3:provided
|  \- ch.qos.logback:logback-core:jar:1.2.3:provided
+- org.apache.httpcomponents:httpclient:jar:4.5.13:compile
|  \- org.apache.httpcomponents:httpcore:jar:4.4.13:compile
+- com.h2database:h2:jar:1.4.200:compile
\- org.springframework.boot:spring-boot-devtools:jar:2.1.18.RELEASE:runtime
   +- org.springframework.boot:spring-boot:jar:2.1.18.RELEASE:compile
   \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.18.RELEASE:compile

Project Details


Checklist

MatKuhr commented 3 years ago

Hi @rafaelferrari, thanks for reaching out!

I see that you are using a version of the SDK that is very outdated. The latest version is 3.39.0 and we generally highly recommend to update it! You can check our Release Notes and also our guide & tutorial to move from 2.X to 3.X (it's pretty straight forward, don't worry).


To the actual issue: In the payload I see you used a placeholder for the data in question: "Content":"base64encodeddatahere".

Could you please clarify, if the actual payload has quotes around the value? So do you have:

  1. "Content": "1234" or
  2. "Content": 1234

Also, could you please add the what the service responds with?


In general it would be great to have a working example with Postman. Because as SDK team we can't really help with the semantics of individual services. For that it's best to reach out to the service owners.

But we should first verify that the SDK sends out the data correctly. And that's easiest to prove with a working request we can compare to.

rafaelferrari commented 3 years ago

Hi @MatKuhr, thanks for the quick reply =) I'll break down the answers in the requested topics.

Version

The project is actually live with this version using a lot of sdk services so there is some risk to upgrade it because of retesting all integrations, but we are currently studying this possibility and actual effort needed to do so.

Payload

The quotes are present in the Content field for the JSON as well, so that would be option 1 - "Content": "1234". I've attached below the actual files used for this test.

Note: I've renamed the extension for second one to txt and updated the BO fields to match my previous example

  • ErASdMfW4AE30hg.jpg - original file
  • ErASdMfW4AE30hg.txt - file retrieved from S/4 after upload Also, I can tell that the string on the Content field for the second file is actually the first file encoded in base64 format.

Service Response

I can't tell unless I try to debug it (which tbh is a little tricky because we don't have access to a s/4 environment unless it's the client's), but no exceptions are thrown by the service class and our REST APIs return with no errors. As I can see the files through the other APIs, I'm assuming the upload operation itself is successful...

Postman Example

We are currently working with another colleague who is able to call the S/4 APIs directly from Postman and hopefully we might be able to obtain a working example in the next couple of days.

rafaelferrari commented 3 years ago

Hi @MatKuhr, a few updates from our side.

Version

We have migrated the cloud-sdk libraries to v3.39.0 in a separate branch, but the exact same error was noticed.

Postman Example

The following collection contains a request in which we were able to successfully upload an attachment. In it we are connecting directly to the service's API on the host. The body type in Postman is set as "binary" and the tested txt is also in the zip file.

S/4 Service

Our colleague who’s working with the S/4 services wants to know which of the two services is the SDK calling for this operation - CV_ATTACHMENT ODATA or API_CV_ATTACHMENT ODATA - just so we are testing the right ones. Do you have that info? We assume it’s the one in the EDMX file (API_CV_ATTACHMENT_ODATA in our case).

Also, can you tell if there is a SDK how-to guide that we could use for reference?

MatKuhr commented 3 years ago

Summary from our call:

Potential workaround:

Future steps:

MatKuhr commented 3 years ago

If the service requires a special payload (e.g. sending the context as plain text only, not the full entity as JSON), you can achieve this with the low-level API of the SDK:

request = new ODataRequestCreate(AttachmentsService.DEFAULT_SERVICE_PATH, attachmentContent.getEntityCollection(), "<your-payload-goes-here>", ODataProtocol.V2);

httpClient = HttpClientAccessor.getHttpClient(destination);
result = request.execute(httpClient);
// optional, in case you want to evaluate the result with some convenience
// EDIT: this is actually package private, my bad
// response = ModificationResponse.of(result, attachmentContent, destination);
ffg91 commented 3 years ago

Thanks @MatKuhr ! In case we want to evaluate the request result, I believe the static method ModificationResponse.of(...); is package protected, so not sure if we can access.

Is there any alternative?

The idea would be to get the specific error message coming from S/4HANA and do something with it.

Thanks!

MatKuhr commented 3 years ago

For error handling please catch the ODataException thrown by execute. You can find out more on the documentation: https://sap.github.io/cloud-sdk/docs/java/features/odata/use-typed-odata-v2-client-in-sap-cloud-sdk-for-java#error-handling

ffg91 commented 3 years ago

Hi @MatKuhr ,

We also have issues with the Download API_CV_ATTACHMENT_ODATA service.

As far as I understood the SDK only supports the fetchAsStream() after you call the Entity GET (i.e. /AttachmentContentSet), right?

Problem is: we get an exception from S/4HANA whenever we call GET /AttachmentContentSet without the /$value tag. Not only via SDK, but also directly in S/4HANA with any http client tool.

In our case, we need to call the GOS services, since we retrieve document keys via /GetAllOriginals.

AttachmentContentSet Get Entity only supports DMS keys, which are nonsense for our use case. So if we call it, it does not cache, but rather throws the exception.

For now, we are exploring the same approach we did for the Upload operation (as you suggested), but we wanted to make sure there is a way to achieve this fully with SDK.

Thank you! Fernando

MatKuhr commented 3 years ago

Okay, interesting. There might be a more convenient workaround:

  1. Create an AttachmentContent object via constructor or builder
  2. Invoke attachToService(AttachmentService.DEFAULT_SERVICE_PATH, destination) on it
  3. Then call fetchAsStream()

I haven't tested this but I think it should let you perform the fetchAsStream() without retrieving the entity first.

Otherwise the low-level API should also work. However, in that case you probably have to evaluate the HTTP response entity manually.

artemkovalyov commented 3 years ago

Hey @ffg91,

Let us know if the solution worked for you and we can close this issue?

ffg91 commented 3 years ago

Hey @MatKuhr
FYI, In my initial tests with your latest suggestion I got an XML parsing error.

Caused by: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[18,5]
Message: The element type "link" must be terminated by the matching end-tag "</link>".

I'd like to investigate a little further to check whether the suggestion actually works before getting back to you - it could be a valuable workaround available.

For now we are doing the same approach as we did for the Upload - using the low-level API. It works fine! 😄

Many thanks for all the great support! 🏆

Hi @artemkovalyov I believe we could close this issue. Thank you!

Johannes-Schneider commented 3 years ago

@ffg91 wrote:

In case we want to evaluate the request result, I believe the static method ModificationResponse.of(...); is package protected, so not sure if we can access.

Is there any alternative?

I'm happy to inform you that we have been working on exposing these static factory methods in our current sprint - the changes have been merged just a few seconds ago 🥳 That means that you can leverage our (previously package-private) factory methods to conveniently convert responses of our low-level API into more elaborate, typed variants starting with the release of version 3.50.0 of the SAP Cloud SDK.

While I cannot make any promises on the actual release date, I'm currently expecting these changes to be publicly available at the end of July/beginning of August (roughly two weeks from now).

Best regards, Johannes