Search code examples
javas4sdk

How to create a new Child node entity via oData without updating or creating the root node


I have created a custom business object in S/4HANA Cloud and it includes one parent node and one child node and also generated Java VDM for S/4HANA Custom OData Service. I combine it with SAP Cloud Platform SDK for service development (which I can't find its SAP Help page anymore).

The scenario is to be able to just add a new child node to an existing root node.

When I tried the POST operation on ParentEntitySet()/childNavigation with other field values, I got an error message 'Invalid method invocation: 'CREATE' method is called on the non-root entity 'YY1_CHILD_ENTITY'.

What can I do to resolve this error?

UPDATE:

Old code caused the error as I described

@Create(serviceName = "CrudService", entity = "ChildEntitySet", sourceEntity = "ParentEntitySet")
    public CreateResponse createABC(CreateRequest createRequest) {

        EntityData entity = createRequest.getData();

        LeaveRequest response;
        try {
            response = new CreateChildCommand(new ErpConfigContext(), new DefaultService(), entity)
                    .execute();
        } catch (HystrixBadRequestException e) {
            return CreateResponse.setError(new ErrorResponseImpl(400, null, e.getMessage(), e.getCause(), null));
        }

        CreateResponse readResponse = CreateResponse.setSuccess().setData(response).response();

        return readResponse;
    }

New code: the error disappeared but new error comes up when I try to post a child entity with navigation.

Inavalid Entity name: No entity with name ParentClass(guid'<parentKey>')/to_ChildEntity in the OData service

But when I query with navigation with the same above url, it's working perfectly. Only create with navigation has this problem.

@Create(serviceName = "CrudService", entity = "ChildEntitySet", sourceEntity = "ParentEntitySet")
    public CreateResponse createChild(CreateRequest createRequest) {

        EntityData entity = createRequest.getData();

        String parentKey = (String) createRequest.getSourceKeys().get(<parentKey>);

        ChildClass response = new ChildClass();
        Map<String, Object> childEntity = new HashMap<>();
        try {
            childEntity = entity.asMap();
            response.setParentKey((String) entity.asMap().get(<parentKey>));
            response.setText((String) entity.asMap().get("Text"));
            response.setCreatedBy((String) entity.asMap().get("CreatedBy"));
            response.setCreatedOn((Calendar) entity.asMap().get("CreatedOn"));

            ParentClass parent = new ParentReadByKeyCommand(new ErpConfigContext(), parentKey)
                    .execute();
            parent.createChildEntity(childEntity);
            return CreateResponse.setSuccess().setData(response).response();
        } catch (Exception e) {
            // Return an instance of QueryResponse containing the error in case of failure
            ErrorResponse errorResponse = ErrorResponse.getBuilder().setMessage(e.getMessage())
                    .setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR).setCause(e).response();
            return CreateResponse.setError(errorResponse);
        }
    }

The below method belongs to ParentClass

public ChildClass createChildEntity(Map<String, Object> childEntity) throws ODataException {
        if (erpConfigContext == null) {
            throw new ODataException(ODataExceptionType.OTHER, "Failed to fetch related objects of type ChildClass.",
                    new IllegalStateException(
                            "Unable to execute OData query. The entity was created locally without an assigned ERP configuration context. This method is applicable only on entities which were retrieved or created using the OData VDM."));
        }
        final StringBuilder odataResourceUrl = new StringBuilder(getEntityCollection());
        odataResourceUrl.append("(");
        odataResourceUrl.append(ODataTypeValueSerializer.of(EdmSimpleTypeKind.Guid).toUri(<parentKey>));
        odataResourceUrl.append(")/");
        odataResourceUrl.append("to_ChildEntity");
        final ODataCreateRequestBuilder builder = ODataCreateRequestBuilder
                .withEntity(getEndpointUrl(), odataResourceUrl.toString()).withBodyAsMap(childEntity);
        final ODataCreateRequest query = builder.build();
        final ODataCreateResult result = query.execute(erpConfigContext);
        final ChildClass entity = result.as(ChildClass.class);
        entity.setErpConfigContext(erpConfigContext);
        return entity;
    }

I'm using

<parent>
        <groupId>com.sap.cloud.servicesdk.prov</groupId>
        <artifactId>projects-parent</artifactId>
        <version>1.19.0</version>
    </parent>

<dependencies>
        <dependency>
            <groupId>com.sap.cloud.s4hana</groupId>
            <artifactId>sdk-parent</artifactId>
            <version>1.11.1</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>com.sap.cloud.s4hana</groupId>
            <artifactId>s4hana-all</artifactId>
            <!-- <version>1.11.1</version> -->
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Solution

  • Some OData APIs only allow to create sub-entities via the root-entity.

    Currently, however, this is not supported by the VDM of the S/4HANA Cloud SDK.

    I will update this answer if there is an update.

    UPDATE: With release 2.4.1 if the S/4 HANA Cloud SDK it is now possible to create entities as a child of another entity.

    The code may look something like this:

    new YourCustomService()
        .createChildEntity(childEntity)
        .asChildOf(parentEntity, ParentEntity.TO_CHILD_ENTITY)
        .execute();
    

    Or a more practical example using the BusinessPartner provided by the SDK:

    new DefaultBusinessPartnerService().createBusinessPartnerAddress(someAddress)
        .asChildOf(
            someBusinessPartner,
            BusinessPartner.TO_BUSINESS_PARTNER_ADDRESS
        )
        .execute();