Как обработать функцию импорта ODATA, не возвращая элемент оболочки

В нашем решении мы вызываем пользовательский API ODATA на основе CDS на стороне S/4HANA, который включает импорт функций. Для этого мы сгенерировали Java-сервис для интерфейса ODATA через плагин maven. Импорт вызываемой функции основан на 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;
    }

Из-за оператора if, отмеченного (X), и того факта, что функция import напрямую возвращает объект, не оборачивая его в ожидаемый член, возвращается нулевой результат.

Таким образом, вопрос для меня заключается в том, может ли SDK иметь возможность обрабатывать этот случай, или же ошибка заключается в том, что S/4HANA API не возвращает результат, соответствующий ODATA.

1 ответ

Решение

К сожалению, нет простого способа настроить это поведение при использовании API. Как вы уже заметили, текущая реализация ожидает, что результат JSON будет содержать объект с тем же именем ключа, что и имя импорта функции, вызываемое в запросе OData.

Вот временное решение вашей проблемы:

  • Переопределите класс реализации FluentHelperFunction с помощью следующего метода "getFunctionName":

    @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 (или же 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 проверяет этот код.

Другие вопросы по тегам