javasap-cloud-platformsap-cloud-sdksap-successfactorssap-cloud-foundry

Generate VDM for SFSF using Java in SAP Cloud SDK: Generated URI is wrong


I'm trying to build an app that reads info from SFSF. For this, I'm using the Virtual Data model generator tool (the maven plugin) with SFSF OData metadata to be able to access the system. I'm following these steps:

mvn archetype:generate "-DarchetypeGroupId=com.sap.cloud.sdk.archetypes" "-DarchetypeArtifactId=scp-cf-tomee" "-DarchetypeVersion=RELEASE"
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
</dependency>

In plugins:

<plugin>
    <groupId>com.sap.cloud.sdk.datamodel</groupId>
    <artifactId>odata-generator-maven-plugin</artifactId>
    <version>3.13.0</version>
    <executions>
        <execution>
            <id>generate-consumption</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputDirectory>${project.basedir}/edmx</inputDirectory>
                <outputDirectory>${project.build.directory}/vdm</outputDirectory>
                <defaultBasePath>/odata/v2</defaultBasePath>
            </configuration>
        </execution>
    </executions>
</plugin>
<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.basedir}/vdm</source>
                            </sources>
                        </configuration>
                    </execution>
    </executions>
</plugin>
package com.sap.sdk;

import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
import com.sap.cloud.sdk.odatav2.connectivity.ODataException;

import com.sap.cloud.sdk.s4hana.connectivity.DefaultErpHttpDestination;
import com.sap.cloud.sdk.s4hana.connectivity.ErpHttpDestination;
import com.sap.cloud.sdk.s4hana.datamodel.odata.namespaces.rcmjobrequisition.JobRequisition;
import com.sap.cloud.sdk.s4hana.datamodel.odata.services.DefaultRCMJobRequisitionService;


@WebServlet("/req")
public class JobReqServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(JobReqServlet.class);

    private final ErpHttpDestination destination = DestinationAccessor.getDestination("sfsf-sdk-dest").asHttp()
            .decorate(DefaultErpHttpDestination::new);


    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
            throws ServletException, IOException {
        try {
            final List<JobRequisition> jobReqs = new DefaultRCMJobRequisitionService()
                .getAllJobRequisition()
                .execute(destination);
            response.setContentType("application/json");
            response.getWriter().write(new Gson().toJson(jobReqs));
        } catch (final ODataException e) {
            logger.error(e.getMessage(), e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.getWriter().write(e.getMessage());
        }
    }
}

With all this (I think I'm not missing anything), I do:

mvn clean install

and:

cf push

Everything works well, the hello world servlet works, but when I try to access /req, I get a: Unable to execute metadata request.

However, I can see that the app is hitting SFSF because if I play with the base path of the service (in the pom.xml) I get 404's coming from SFSF.

Checking everything, I see this when the VDM generator is running: 1. This is the base path I'm giving in the pom:

<defaultBasePath>/odata/v2</defaultBasePath>
  1. I can see the generator picking that path correctly:
[main] INFO com.sap.cloud.sdk.datamodel.odata.generator.DataModelGenerator -   Default base path:              /odata/v2/
  1. But this is what the generator processes:
[main] INFO com.sap.cloud.sdk.datamodel.odata.generator.ODataToVdmGenerator -   Title: RCMJobRequisition
[main] INFO com.sap.cloud.sdk.datamodel.odata.generator.ODataToVdmGenerator -   Raw URL: /odata/v2/SFODataSet
[main] INFO com.sap.cloud.sdk.datamodel.odata.generator.ODataToVdmGenerator -   Java Package Name: rcmjobrequisition
[main] INFO com.sap.cloud.sdk.datamodel.odata.generator.ODataToVdmGenerator -   Java Class Name: RCMJobRequisition

Clearly, that SFODataSet in the URL is not correct. When the app runs, it's tring to get the metadata from .../odata/v2/SFODataSet/$metadata, and that's why it's not finding it. That SFODataSet is coming from the SFSF metadata:

<Schema Namespace="SFODataSet" xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:sf="http://www.successfactors.com/edm/sf" xmlns:sap="http://www.sap.com/Protocols/SAPData">
      <EntityContainer Name="EntityContainer" m:IsDefaultEntityContainer="true">
        <EntitySet Name="JobOfferTemplate_Standard_Offer_Details" EntityType="SFOData.JobOfferTemplate_Standard_Offer_Details" sap:label="JobOfferTemplate_Standard_Offer_Details" sap:creatable="false" sap:updatable="false" sap:upsertable="false" sap:deletable="false">
          <Documentation>
            <Summary>Job Requisition Template</Summary>
            <LongDescription>These entities represent the job requisition template as defined in provisioning.</LongDescription>
            <sap:tagcollection>
              <sap:tag>Recruiting (RCM)</sap:tag>
              <sap:tag>RCM - Job Requisition</sap:tag>
            </sap:tagcollection>
          </Documentation>
        </EntitySet>
        <EntitySet Name="JobRequisitionLocale" EntityType="SFOData.JobRequisitionLocale" sap:label="JobRequisitionLocale" sap:creatable="false" sap:updatable="false" sap:upsertable="false" sap:deletable="false">
          <Documentation>
...

I can't find the way for this to work. Can you help me find the issue here?

I'm using:

Edit: SFSF metadata files are available in https://api.sap.com/ The one I'm using for this app is for SFSF - Job Requisition, available here: https://api.sap.com/api/RCMJobRequisition/overview

From there, you can download the EDMX specification. These are "mock" API's, not connected to a real SFSF instance, but the problem is the same.

To do this I'm following two blogs mainly:

Also, I removed tha last part as I will open a separate question: SFSF OData call: Failed to convert response into ODataFeed: An 'EdmSimpleTypeException' occurred

Thanks,

kepair


Solution

  • I will start of with a partial answer and edit in more information later if needed.

    Regarding the URL:

    The behaviour you observe is intentional. The full URL of a request will be assembled as follows: Destination URL + service path + service name + entity + '?' + query parameters. So in your case that might be:

    https://my.host.domain/odata/v2/JobRequisitions/MyEntity
    Destination: https://my.host.domain
    Service Path: /odata/v2
    Service name: JobRequisitions
    Entity: MyEntity
    

    The generator assembles the default base path from service path + service name. The service name will actually be pulled from the namespace of the EDMX. That is why the URL of your service is being generated the way it is.

    The reason for this is simple: One might want to generate a VDM for multiple services at the same time. All of these services are exposed under the same endpoint except for the service name itself. In order to generate all the VDMs with one configuration we can specify the "service path" in the generator and the generator pulls the service name from the EDXM itself.

    So that means that your approach of overwriting the generated base path should work:

    final List<JobRequisition> jobReqs = new DefaultRCMJobRequisitionService()
                    .withServicePath("odata/v2/JobRequisition")
                    .getAllJobRequisition()
                    .execute(destination);
    

    The error message at the very end of your question looks a bit like a problem with parsing to me. But in order to tackle that one further we would need the full stack trace and the HTTP log output. Also, we can only reproduce the problem if we have access to the metadata. The link you provided requires authorization through username/password.

    Since your question above is already quite comprehensive I would recommend that you separate these two problems and create a new question, if this really turns out to be an independent problem. This will also make both questions more relevant for others.