如何处理 ODATA 函数导入不返回包装器元素
How to process ODATA Function Import not returning a wrapper element
在我们的解决方案中,我们在 S/4HANA 端调用基于 CDS 的自定义 ODATA API,其中包括函数导入。为此,我们通过 maven-plugin 为 ODATA 接口生成了 Java 服务。调用的函数导入基于 BOPF,生成的实现没有 return 符合 SAP Cloud SDK 预期的结果,即生成的 JSON 对象具有函数名称的成员导入。
我们检查了是否可以更改生成的 ODATA 实现,但在 S/4HANA 中没有找到这样做的配置。
我在调用时调试了SDK实现,在FunctionImportResponseParser中发现了如下代码,做出了这样的假设:
<T> T getEntityFromResponse(
final InputStream responseContent,
final String edmFunctionImportName,
final Class<? extends T> entityJavaType )
throws IOException,
IllegalArgumentException
{
final JsonObject responseJsonObject = getJsonObjectFromResponse(responseContent);
(X) if( responseJsonObject.has(edmFunctionImportName) ) {
final JsonElement jsonElement = responseJsonObject.get(edmFunctionImportName);
return getEntityFromJsonElement(jsonElement, entityJavaType);
}
return null;
}
由于 (X) 标记的 if 语句以及函数直接导入 returns 对象未将其包装在预期成员中的事实,结果为 null returned .
所以我的问题是,SDK 是否也可以处理这种情况,或者错误是否出在 S/4HANA API 而不是 return正在生成符合 ODATA 的结果。
不幸的是,在使用 API 时没有简单的方法来自定义此行为。正如您已经注意到的,当前实现期望 JSON 结果包含一个对象,该对象的键名与 OData 请求中调用的 函数导入 名称相同。
这是针对您的问题的临时解决方法:
使用以下 "getFunctionName" 方法覆盖 FluentHelperFunction 实现 class:
@Override
@Nonnull
protected String getFunctionName()
{
final String callingMethod = Thread.currentThread().getStackTrace()[2].getMethodName();
if( "generatePath".equals(callingMethod) ) {
return "TheFunctionNameInUrlPath";
}
if( "executeSingle".equals(callingMethod) ) {
return "TheKeyOfODataResponse";
}
throw new IllegalStateException("This should not happen.");
}
虽然这不是很好的代码,但我们将检查如何在未来使用 SAP Cloud SDK 使它变得更容易。
更新: 不幸的是,您收到的 JSON 响应没有将内容包装到根元素 "d" 内的另一个对象中。这使得问题更难解决。
如果您仍想在这种情况下使用 SAP Cloud SDK,则需要调整一些内部代码。请尝试对 execute
(或 executeSingle
)方法进行以下更改,而不是上面列出的 getFunctionName
:
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import com.google.common.io.CharStreams;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.json.JsonSanitizer;
import com.sap.cloud.sdk.odatav2.connectivity.ODataExceptionType;
import com.sap.cloud.sdk.odatav2.connectivity.ODataGsonBuilder;
import com.sap.cloud.sdk.result.GsonResultElementFactory;
import com.sap.cloud.sdk.result.ResultElement;
import org.apache.http.HttpEntity;
...
@Override
@Nullable
public T execute( @Nonnull final ErpConfigContext configContext )
throws ODataException
{
final HttpEntity httpEntity = accessibleQuery(configContext);
final ProposalHeader response;
try( final InputStream content = httpEntity.getContent() ) {
final String rawContent = CharStreams.toString(new InputStreamReader(content, StandardCharsets.UTF_8));
final JsonElement responseJsonElement = new JsonParser().parse(JsonSanitizer.sanitize(rawContent));
// select JSON root object
final JsonElement jsonElement = responseJsonElement.getAsJsonObject().getAsJsonObject("d");
// deserialize contents
final GsonResultElementFactory elementFactory = new GsonResultElementFactory(ODataGsonBuilder.newGsonBuilder());
final ResultElement resultElement = elementFactory.create(jsonElement);
response = resultElement.getAsObject().as(getEntityClass());
}
catch( final IOException | IllegalArgumentException | JsonSyntaxException e ) {
throw new ODataException(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED, "Failed to read OData result.", e);
}
return response;
}
private HttpEntity accessibleQuery( @Nonnull final ErpConfigContext configContext ) {
try {
final Method query = FluentHelperFunction.class.getDeclaredMethod("query", ErpConfigContext.class);
query.setAccessible(true);
Object httpEntityRaw = query.invoke(this, configContext);
return HttpEntity.class.cast(httpEntityRaw);
}
catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassCastException e) {
// log error
// throw exception
return null;
}
}
您可以将通用类型 T
更改为您预期的响应 class。还要更改行 // log error
和 // throw exception
以匹配您的应用程序用例,以便将来可以轻松处理错误。此外,您应该对此代码引入一些 null
检查。
在我们的解决方案中,我们在 S/4HANA 端调用基于 CDS 的自定义 ODATA API,其中包括函数导入。为此,我们通过 maven-plugin 为 ODATA 接口生成了 Java 服务。调用的函数导入基于 BOPF,生成的实现没有 return 符合 SAP Cloud SDK 预期的结果,即生成的 JSON 对象具有函数名称的成员导入。
我们检查了是否可以更改生成的 ODATA 实现,但在 S/4HANA 中没有找到这样做的配置。
我在调用时调试了SDK实现,在FunctionImportResponseParser中发现了如下代码,做出了这样的假设:
<T> T getEntityFromResponse(
final InputStream responseContent,
final String edmFunctionImportName,
final Class<? extends T> entityJavaType )
throws IOException,
IllegalArgumentException
{
final JsonObject responseJsonObject = getJsonObjectFromResponse(responseContent);
(X) if( responseJsonObject.has(edmFunctionImportName) ) {
final JsonElement jsonElement = responseJsonObject.get(edmFunctionImportName);
return getEntityFromJsonElement(jsonElement, entityJavaType);
}
return null;
}
由于 (X) 标记的 if 语句以及函数直接导入 returns 对象未将其包装在预期成员中的事实,结果为 null returned .
所以我的问题是,SDK 是否也可以处理这种情况,或者错误是否出在 S/4HANA API 而不是 return正在生成符合 ODATA 的结果。
不幸的是,在使用 API 时没有简单的方法来自定义此行为。正如您已经注意到的,当前实现期望 JSON 结果包含一个对象,该对象的键名与 OData 请求中调用的 函数导入 名称相同。
这是针对您的问题的临时解决方法:
使用以下 "getFunctionName" 方法覆盖 FluentHelperFunction 实现 class:
@Override @Nonnull protected String getFunctionName() { final String callingMethod = Thread.currentThread().getStackTrace()[2].getMethodName(); if( "generatePath".equals(callingMethod) ) { return "TheFunctionNameInUrlPath"; } if( "executeSingle".equals(callingMethod) ) { return "TheKeyOfODataResponse"; } throw new IllegalStateException("This should not happen."); }
虽然这不是很好的代码,但我们将检查如何在未来使用 SAP Cloud SDK 使它变得更容易。
更新: 不幸的是,您收到的 JSON 响应没有将内容包装到根元素 "d" 内的另一个对象中。这使得问题更难解决。
如果您仍想在这种情况下使用 SAP Cloud SDK,则需要调整一些内部代码。请尝试对 execute
(或 executeSingle
)方法进行以下更改,而不是上面列出的 getFunctionName
:
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import com.google.common.io.CharStreams;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.json.JsonSanitizer;
import com.sap.cloud.sdk.odatav2.connectivity.ODataExceptionType;
import com.sap.cloud.sdk.odatav2.connectivity.ODataGsonBuilder;
import com.sap.cloud.sdk.result.GsonResultElementFactory;
import com.sap.cloud.sdk.result.ResultElement;
import org.apache.http.HttpEntity;
...
@Override
@Nullable
public T execute( @Nonnull final ErpConfigContext configContext )
throws ODataException
{
final HttpEntity httpEntity = accessibleQuery(configContext);
final ProposalHeader response;
try( final InputStream content = httpEntity.getContent() ) {
final String rawContent = CharStreams.toString(new InputStreamReader(content, StandardCharsets.UTF_8));
final JsonElement responseJsonElement = new JsonParser().parse(JsonSanitizer.sanitize(rawContent));
// select JSON root object
final JsonElement jsonElement = responseJsonElement.getAsJsonObject().getAsJsonObject("d");
// deserialize contents
final GsonResultElementFactory elementFactory = new GsonResultElementFactory(ODataGsonBuilder.newGsonBuilder());
final ResultElement resultElement = elementFactory.create(jsonElement);
response = resultElement.getAsObject().as(getEntityClass());
}
catch( final IOException | IllegalArgumentException | JsonSyntaxException e ) {
throw new ODataException(ODataExceptionType.ODATA_OPERATION_EXECUTION_FAILED, "Failed to read OData result.", e);
}
return response;
}
private HttpEntity accessibleQuery( @Nonnull final ErpConfigContext configContext ) {
try {
final Method query = FluentHelperFunction.class.getDeclaredMethod("query", ErpConfigContext.class);
query.setAccessible(true);
Object httpEntityRaw = query.invoke(this, configContext);
return HttpEntity.class.cast(httpEntityRaw);
}
catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassCastException e) {
// log error
// throw exception
return null;
}
}
您可以将通用类型 T
更改为您预期的响应 class。还要更改行 // log error
和 // throw exception
以匹配您的应用程序用例,以便将来可以轻松处理错误。此外,您应该对此代码引入一些 null
检查。