davidmoten / odata-client

Java client generator for a service described by OData CSDL 4.0 metadata. Includes Microsoft Graph clients (v1.0 and Beta), Graph Explorer client, Analytics for DevOps, Dynamics CRM clients
Apache License 2.0
34 stars 8 forks source link

Post method write data to CRM but give Exception when building response #450

Open vaughnmb opened 4 months ago

vaughnmb commented 4 months ago

I just found this library today. Sorry, if I there is a better place to post this question

I run this code

Account account = Account.builderAccount()
                .accountnumber("987654329")
                .name("test company")
                .address1_line1("test add1")
                .address1_line2("test add2")
                .address1_line3("test add3")
                .address1_city("test ciyt")
                .build();
Account account2 = client.accounts().post(account);

and it successfully writes data to Dynamics CRM, however, it returns the below message.

java.lang.IllegalArgumentException: argument "content" is null at com.fasterxml.jackson.databind.ObjectMapper._assertNotNull(ObjectMapper.java:5054) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3813) at com.github.davidmoten.odata.client.Serializer.deserialize(Serializer.java:96) at com.github.davidmoten.odata.client.internal.RequestHelper.submitAny(RequestHelper.java:194) at com.github.davidmoten.odata.client.internal.RequestHelper.postAny(RequestHelper.java:159) at com.github.davidmoten.odata.client.internal.RequestHelper.post(RequestHelper.java:131) at com.github.davidmoten.odata.client.CollectionPageEntityRequest.post(CollectionPageEntityRequest.java:60) at com.github.davidmoten.odata.client.CollectionEntityRequestOptionsBuilder.post(CollectionEntityRequestOptionsBuilder.java:199) at com.github.davidmoten.odata.client.CollectionPageEntityRequest.post(CollectionPageEntityRequest.java:97)

The issue is that in this code the "String text" variable is null.

        // get the response
        HttpResponse response = cp.context().service().submit(method, cp.toUrl(), h, json, options);

        // TODO could be tightened to 201 for POST create but POST Action calls need to
        // accept any successful code
        checkResponseCodeOk(cp, response);

        String text = response.getText();
        // deserialize
        Class<? extends T> c = getSubClass(cp, contextPath.context().schemas(), responseClass,
                text);
        // check if we need to deserialize into a subclass of T (e.g. return a
        // FileAttachment which is a subclass of Attachment)
        return cp.context().serializer().deserialize(text, c, contextPath, false);

How do I make it return data in the Response? Am I missing something in my method calls?

davidmoten commented 4 months ago

Righto, can you tell me what the returned json is so I can figure out the deserialisation problem?

davidmoten commented 4 months ago

Is Account your extension or part of base CRM? If an extension can you supply your odata metadata?

vaughnmb commented 4 months ago

It is part of the base Dynamics CRM

Yahoo Mail: Search, Organize, Conquer

On Wed, May 15, 2024 at 10:05 PM, Dave @.***> wrote:

Is Account your extension or part of base CRM? If an extension can you supply your odata metadata?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

davidmoten commented 4 months ago

Thanks. I've just read the specification which says that a post "MUST contain the resource created" (and thus can't return nothing which would give the null problem you saw) if response code is 201. What status code was returned?

9.1.2 Response Code 201 Created A Create Entity, Create Media Entity, Create Link or Invoke Action request that successfully creates a resource returns 201 Created. In this case, the response body MUST contain the resource created.

9.1.4 Response Code 204 No Content A request returns 204 No Content if the requested resource has the null value, or if the service applies a return=minimal preference. In this case, the response body MUST be empty.

vaughnmb commented 4 months ago

The Response Code is a 204

vaughnmb commented 4 months ago

ok. I got it to return back a 201 and the Account data from the POST. Just needed to add "requestHeader" to the line of code below.

client.accounts().requestHeader("Prefer", "return=representation").post(account);

Thanks for helping with this

vaughnmb commented 4 months ago

Is it possible to do these two things with this project?

https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/create-entity-web-api#create-related-table-rows-in-one-operation

https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/create-entity-web-api#associate-table-rows-on-create

davidmoten commented 4 months ago

ok. I got it to return back a 201 and the Account data from the POST. Just needed to add "requestHeader" to the line of code below.

client.accounts().requestHeader("Prefer", "return=representation").post(account);

Thanks for helping with this

Nice, glad you have a workaround. I will address this though. The fact that this is a possibility means that I should return Optional<Account> from the post() method (a breaking change). I'll ponder it a bit.

davidmoten commented 4 months ago

Is it possible to do these two things with this project?

https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/create-entity-web-api#create-related-table-rows-in-one-operation

https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/create-entity-web-api#associate-table-rows-on-create

In short, you can do anything with this project using custom requests.

NavigationProperties that have ContainsTarget="True" will be listed as settable properties in an Account builder so that single operation efficiency can happen. I don't support the Partner attribute currently which is what the create table row call relates to and frankly the ability to effectively contain those properties, enabling a single post call, is not apparent in the odata 4 specification so I assume that this is an out-of-spec offering by Microsoft and might be in their custom metadata annotations (no idea where to find them, have seen such a thing for Graph service only).

So you can use a CustomRequest to do anything but you can get a mix by setting unmapped fields. Would be nice to be able to set UnmappedFields in the builder (I'll look at that sometime) but it is doable using the with* mutators. Here's an example based on your first link:

    @Test
    public void test() {
        Account a = Account.builderAccount() //
                .name("Sample Account") //
                .build() //
                .withUnmappedField( //
                        "primaryContactId", //
                        Contact.builderContact() //
                                .firstname("John") //
                                .lastname("Smith") //
                                .build()) //
                .withUnmappedField("opportunity_customer_accounts", //
                        Lists.newArrayList( //
                                Maps //
                                        .put("name", (Object) "Opportunity associated to Sample Account")
                                        .put("Opportunity_Tasks", //
                                                Lists.newArrayList( //
                                                        Maps.put("subject", "Task associated to opportunity").build()))
                                        .build()));

        System.out.println(Serializer.INSTANCE.serializePrettyPrint(a));
        if (false) {
            microsoft.dynamics.crm.container.System client = ...
            client.accounts().post(a);
        }
    }

Output is

{
  "@odata.type" : "Microsoft.Dynamics.CRM.account",
  "name" : "Sample Account",
  "opportunity_customer_accounts" : [ {
    "Opportunity_Tasks" : [ {
      "subject" : "Task associated to opportunity"
    } ],
    "name" : "Opportunity associated to Sample Account"
  } ],
  "primaryContactId" : {
    "@odata.type" : "Microsoft.Dynamics.CRM.contact",
    "firstname" : "John",
    "lastname" : "Smith"
  }
}

The catch is that the create table row docs indicate a 204 response again. Might be able to give it that header and have it work, you could try. I'll look very shortly at returning Optional<> from post operations (and perhaps others too, I'll poke around) so that we support the 204 No Content case properly.

davidmoten commented 4 months ago

post and patch methods now return Optional<T> so that we have the No-Content response case covered. Available in 0.2.0 on Maven Central now. Let me know how you go.