首页 > 解决方案 > 如何处理 ODATA 函数导入不返回包装元素

问题描述

在我们的解决方案中,我们在 S/4HANA 端调用基于 CDS 的自定义 ODATA API,其中包括函数导入。为此,我们通过 maven-plugin 为 ODATA 接口生成了 Java 服务。调用的函数导入是基于 BOPF 的,生成的实现没有返回符合 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 语句以及函数 import 直接返回对象而不将其包装在预期成员中的事实,因此返回结果 null。

所以对我来说,问题是,如果 SDK 也可以处理这种情况,或者错误是否在于 S/4HANA API 没有返回符合 ODATA 的结果。

标签: sap-cloud-sdk

解决方案


不幸的是,在使用 API 时没有简单的方法来自定义此行为。正如您已经注意到的那样,当前实现期望 JSON 结果包含一个与OData 请求中调用的函数导入名称具有相同键名称的对象。

这是您的问题的临时解决方法:

  • 使用以下“getFunctionName”方法覆盖FluentHelperFunction实现类:

    @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,则需要调整一些内部代码。getFunctionName请尝试以下对execute(or executeSingle) 方法的更改,而不是上面的列表:

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为您预期的响应类。还要更改行// log error// throw exception匹配您的应用程序用例,以便将来可以轻松完成错误处理。此外,您应该null对此代码进行一些检查。


推荐阅读