4

I created an APM Business Application in SAP Web IDE Full-Stack.

The S/4HANA SDK reads business partners with the S/4HANA API. Custom business partner data is persisted in a SAP Cloud Platform HDI container. The business partner entity has an 0..1 association with the custom entity.

The OData navigation property is successful when the custom data exists but returns an Null Pointer Exception when it does not exist. The OData $expand is not successful in either case, returning error while trying to invoke the method java.util.Map.size() of a null object loaded from local variable 'm'. I may be incorrectly expecting the associated entity to return the key property’s value but not the values of the other properties.

Below are the data model, service, business partner operations, error, and logs.

Data model CDS

entity BusinessPartner {
    Key BusinessPartner     : String(10);
    LastName                : String(40);
    FirstName               : String(40);
    status                  : Association to PartnerStatus;
}

entity PartnerStatus {
    Key BusinessPartner     : String(10);
    StatusConfirmed         : String(1);
}

Service CDS

service BusinessPartnerService {
    @cds.persistence.skip
    entity BusinessPartner @readonly as projection on s4c.BusinessPartner;

    entity PartnerStatus   @readonly as projection on s4c.PartnerStatus;
}

Business Partner @Query and @Read

@Query(serviceName = "BusinessPartnerService", entity = "BusinessPartner")
public QueryResponse queryPartners(QueryRequest queryRequest) throws ServletException {
    final List<BusinessPartner> businessPartners;

    try {
        businessPartners = new DefaultBusinessPartnerService()
            .getAllBusinessPartner()
            .top(5)
            .execute();
    } catch (ODataException e) {
        throw new ServletException(e);
    }

    QueryResponse queryResponse = QueryResponse.setSuccess().setData(businessPartners).response();

    return queryResponse;
}

@Read(serviceName = "BusinessPartnerService", entity = "BusinessPartner")
public ReadResponse readPartner(ReadRequest readRequest) throws ServletException {
    String id = String.valueOf(readRequest.getKeys().get("BusinessPartner"));

    final BusinessPartner businessPartner;

    try {
        businessPartner = new DefaultBusinessPartnerService()
            .getBusinessPartnerByKey(id)
            .execute();
    } catch (ODataException e) {
        throw new ServletException(e);
    }

    ReadResponse readResponse = ReadResponse.setSuccess().setData(businessPartner).response();

    return readResponse;
}

Error

<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <code>CDSRuntimeException.NO_ENTITY_FOUND</code>
    <message xml:lang="en-US">No Entity found for the URL.</message>
</error>

Logs

  2018-11-06T11:02:50.53-0500 [APP/PROC/WEB/0] OUT { "written_at":"2018-11-06T16:02:50.538Z","written_ts":1056610886992277,"component_id":"df464a53-1905-4c4b-bcc5-ef59761d2218","component_name":"ge2qqww1rr7SxSEK-apm-s4c-srv","DCComponent":"","organization_name":"-","component_type":"application","space_name":"dev","component_instance":"0","organization_id":"-","correlation_id":"-","CSNComponent":"","space_id":"5e8e1eb0-f9ca-40d8-8ba9-1880ca345628","Application":"ge2qqww1rr7SxSEK-apm-s4c-srv","container_id":"10.0.137.3","type":"log","logger":"com.sap.cloud.sdk.service.prov.v2.rt.core.CloudSDKODataErrorCallback","thread":"http-nio-0.0.0.0-3000-exec-2","level":"ERROR","categories":[],"msg":"Request URL: https://ge2qqww1rr7sxsek-apm-s4c-srv.cfapps.us10.hana.ondemand.com/odata/v2/BusinessPartnerService/BusinessPartner?$expand=status\nStatusCode:500","stacktrace":["java.lang.NullPointerException: while trying to invoke the method java.util.Map.size() of a null object loaded from local variable 'm'","\tat java.util.HashMap.putMapEntries(HashMap.java:501)","\tat java.util.HashMap.putAll(HashMap.java:785)","\tat com.sap.gateway.core.api.provider.data.BaseDataProvider.getSingleNavigationPathResult(BaseDataProvider.java:837)","\tat com.sap.gateway.core.api.provider.data.BaseDataProvider.readExpandedEntitySet(BaseDataProvider.java:915)","\tat com.sap.cloud.sdk.service.prov.v2.data.provider.CXSDataProvider.readExpandedEntitySet(CXSDataProvider.java:724)","\tat com.sap.cloud.sdk.service.prov.v2.rt.data.provider.HybridDataProvider.readExpandedEntitySet(HybridDataProvider.java:298)","\tat com.sap.gateway.core.api.provider.data.GenericODataProcessor.readEntitySet(GenericODataProcessor.java:895)","\tat org.apache.olingo.odata2.core.Dispatcher.dispatch(Dispatcher.java:77)","\tat org.apache.olingo.odata2.core.ODataRequestHandler.handle(ODataRequestHandler.java:131)","\tat org.apache.olingo.odata2.core.servlet.ODataServlet.handleRequest(ODataServlet.java:216)","\tat org.apache.olingo.odata2.core.servlet.ODataServlet.handle(ODataServlet.java:115)","\tat org.apache.olingo.odata2.core.servlet.ODataServlet.service(ODataServlet.java:85)","\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:742)","\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)","\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)","\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)","\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)","\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)","\tat com.sap.cloud.sdk.cloudplatform.servlet.RequestContextServletFilter.lambda$doFilter$0(RequestContextServletFilter.java:171)","\tat com.sap.cloud.sdk.cloudplatform.servlet.RequestContextCallable.call(RequestContextCallable.java:95)","\tat com.sap.cloud.sdk.cloudplatform.servlet.RequestContextServletFilter.doFilter(RequestContextServletFilter.java:173)","\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)","\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)","\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)","\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)","\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)","\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)","\tat com.sap.xs.java.valves.ErrorReportValve.invoke(ErrorReportValve.java:66)","\tat ch.qos.logback.access.tomcat.LogbackValve.invoke(LogbackValve.java:191)","\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)","\tat com.sap.xs.jdbc.datasource.valve.JDBCValve.invoke(JDBCValve.java:62)","\tat com.sap.xs.security.UserInfoValve.invoke(UserInfoValve.java:19)","\tat com.sap.xs.statistics.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:43)","\tat com.sap.xs.logging.catalina.RuntimeInfoValve.invoke(RuntimeInfoValve.java:40)","\tat org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:685)","\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)","\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)","\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)","\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800)","\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)","\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)","\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)","\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)","\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)","\tat java.lang.Thread.run(Thread.java:836)"] }
  • Could you provide the full stack trace from the log? – Henning Heitkötter Nov 06 '18 at 17:22
  • Another question: did you implement anything in addition to the code above for retrieving the associated entity `status`? Do you have an example of a successful call with expand? – Henning Heitkötter Nov 06 '18 at 17:24
  • @HenningHeitkötter I did not implement anything in addition to the above code for retrieving the `status` entity. – Scott Stefanich Nov 06 '18 at 18:04
  • @HenningHeitkötter @HenningHeitkötter I incorrectly said the $expand operation was successful. The URI to the navigation property is successful (/BusinessPartner('1000000')/status) if an PartnerStatus entry exists in the HDI container. The following error message is returned from $expand: `while trying to invoke the method java.util.Map.size() of a null object loaded from local variable 'm'` – Scott Stefanich Nov 06 '18 at 18:10

2 Answers2

2

I used CDSDataSourceHandler to read the local entity.

If the local entity exists, it is returned. If the local entity does not exist, the keys are passed to the entity from the source entity.

private static Connection getConnection() {
    Connection conn = null;
    Context ctx;
    try {
        ctx = new InitialContext();
        conn = ((DataSource) ctx.lookup("java:comp/env/jdbc/java-hdi-container")).getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return conn;
}

@Read(serviceName = "BusinessPartnerService", entity = "PartnerStatus", sourceEntity = "BusinessPartner")
public ReadResponse readPartnerStatus(ReadRequest readRequest) {
    CDSDataSourceHandler dsHandler = DataSourceHandlerFactory.getInstance().getCDSHandler(getConnection(), readRequest.getEntityMetadata().getNamespace());

    EntityData ed = null;

    try {
        ed = dsHandler.executeRead(readRequest.getEntityMetadata().getName(), readRequest.getKeys(), readRequest.getEntityMetadata().getElementNames());
    } catch (CDSException e) { }

    if (ed != null) {
        return ReadResponse.setSuccess().setData(ed).response();

    } else {
        Map<String, Object> keys = readRequest.getKeys();
        Object keyObject = keys.get("BusinessPartner");
        String id = (String)keyObject;

        return ReadResponse.setSuccess().setData(ImmutableMap.of("BusinessPartner", id, "StatusConfirmed", "X")).response();
    }
}

The OData $expand is then successful for both the entity and entity set.

1

The join across the remote entity and the local entity does not happen automatically. You have to implement this manually using a Read operation for the associated entity PartnerStatus that also specifies the source entity for the navigation BusinessPartner. This method would look as follows (as you can see, in this example I am always returning dummy data):

@Read(serviceName = "BusinessPartnerService",
    entity = "PartnerStatus",
    sourceEntity = "BusinessPartner")
public ReadResponse readPartnerStatus(ReadRequest readRequest) throws ServletException {
    return ReadResponse.setSuccess().setData(
            ImmutableMap.of("BusinessPartner", "1003764", "StatusConfirmed", "X"))
        .response();
}

The documentation about the Read operation contains more details on this concept.

You would now need to access the database, for example, using JPA (I have not tried this out) in the handler. The documentation has a section on Using JPA in Custom Handlers.

  • Thank you, Henning. The explanation, example, and documentation clarify the join behavior and explain the concepts. If you could clarify one behavior, why does the OData navigation property automatically retrieve the local entity when it exists? For example, `/BusinessPartner('1000000')/status` returns the status property with the following properties and values: ` 1000000 A `. – Scott Stefanich Nov 09 '18 at 15:18