2

I've written a simple webService in Java 8, on Eclipse Photon, using RestTemplate to post (using postForObject) an object (called patentListWrapper) that wraps a List of objects (called PatentDetails ). I post from a Java client (called MainWsClient ) , then set a value in patentDetails on the server side and read the patentListWrapper object back at the client. It works fine when the server side (program SpringWebServiceHello) uses old Spring MVC 4 technology with only 1 jar file (Spring-web.5.07.RELEASE.jar) following this - serverSideExample ie a web.xml and rest-servlet.xml files controlling the access point. I then wrote another server side program (PndGuidRequestWs) using SpringBoot 2.03 with Spring 5.07 jars, and Maven ,with an identicle @RequestMapping method but no web.xml file and the access point defined in the application.properties file:

server.port=8082
server.servlet.path=/
#spring.mvc.servlet.path=/
#server.servlet.contextPath=/

When I call this new server program using this client - ARC it also works fine but when I call it using the same java client and exactly the same request (accept for a different url obviously). I get a 400 error:

2018-12-18 16:56:53,861 [main] INFO  - Running MainWsClient with name = DS fileType = post3
2018-12-18 16:56:54,101 [main] DEBUG - Created POST request for "http://localhost:8082/guidRequest/xmlList"
2018-12-18 16:56:54,145 [main] DEBUG - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
2018-12-18 16:56:54,152 [main] DEBUG - Writing [com.springservice.client.PatentListWrapper@4ba2ca36] using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@3444d69d]
2018-12-18 16:56:54,384 [main] DEBUG - POST request for "http://localhost:8082/guidRequest/xmlList" resulted in 400 (null); invoking error handler
2018-12-18 16:56:54,387 [main] ERROR - DS1B org.springframework.web.client.HttpClientErrorException: 400 null

The non working ,PndGuidRequestWs, server side has:

@RestController
public class PndController {

@RequestMapping(value = "/guidRequest/xmlList", method = RequestMethod.POST, produces = { "application/xml" } )
public PatentListWrapper guidSearchList(@RequestBody  PatentListWrapper patentListWrapper) {

    for (PatentDetails pd : patentListWrapper.getPatentList())
    {
        pd.setGuid("guidSetOnServer3");
    }

    return patentListWrapper;
  }

}

The working (SpringWebServiceHello) server side is identicle except for :

value = "/service/greeting/xml/post2"

The Java client has:

public void runCode(String name , String fileType)
{

 String url;

 if (fileType.equalsIgnoreCase("post2")) {
        url = "http://localhost:8080/SpringWebServiceHello/service/greeting/xml/post2";
        // This method is identicle to postToPndGuidRequestWs() but this method works fine.
        postToSpringWebServiceHello(url);
    }else if (fileType.equalsIgnoreCase("post3")) {
        url = "http://localhost:8082/guidRequest/xmlList";      
        // This method gives 404 error          
        postToPndGuidRequestWs(url);
    }   
}

private void postToPndGuidRequestWs(String url) 
{

    PatentListWrapper patentListWrapper = new PatentListWrapper();
    PatentDetails pd = new PatentDetails("CN","108552082","A","00000000",12345,"guidIn");

    List<PatentDetails> patentList = new ArrayList<PatentDetails>();
    patentList.add(pd);
    patentListWrapper.setPatentList(patentList);

    RestTemplate restTemplate = new RestTemplate();

    /* HttpHeaders headers = new HttpHeaders();
    headers.add("header_name", "header_value");
    headers.setContentType(MediaType.APPLICATION_XML);
    HttpEntity<PatentListWrapper> request = new HttpEntity<PatentListWrapper>(patentListWrapper, headers); */

    /*List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
    Jaxb2RootElementHttpMessageConverter jaxbMessageConverter = new Jaxb2RootElementHttpMessageConverter();
    List<MediaType> mediaTypes = new ArrayList<MediaType>();
    mediaTypes.add(MediaType.APPLICATION_XML);
    jaxbMessageConverter.setSupportedMediaTypes(mediaTypes);
    messageConverters.add(jaxbMessageConverter);
    restTemplate.setMessageConverters(messageConverters);*/

    /* headers = new HttpHeaders();
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
    HttpEntity<String> entity = new HttpEntity<>("parameters", headers);*/


    try {
        patentListWrapper = restTemplate.postForObject(
                url,
                patentListWrapper,
                PatentListWrapper.class);


        logger.debug("DS1A employee obj returned. guid = " +  patentListWrapper.getPatentList().get(0).getGuid());
    }catch(Exception e) {
        logger.error("DS1B " + e);      
    }   
}

}

ie fileType="post2" calls SpringWebServiceHello, fileType="post3" calls PndGuidRequestWs. As you can see i've tried several commented out solutions but nothing works. Since the only real difference between the 2 server side programs is that none working one uses Spring boot and the working one doesn't the problem must be in the SpringBoot setup ie directory structure, application.properties or pom.xml. My pom.xml has:

<?xml version="1.0" encoding="UTF-8"?>

http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0

<groupId>com.clarivate</groupId>
<artifactId>pndguidrequestws</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>

<name>pndGuidRequestWs</name>
<description>Guid request webService</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <start-class>com.clarivate.pndguidrequestws.PndGuidRequestWsApplication</start-class>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>11.2.0.1.0</version> 
      <!--    <scope>provided</scope> --> <!-- DS insert for unix -->
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-jdbc</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- Implementing XML Representation for Spring Boot Services -->
    <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

    <!-- httpcomponents jars are Required by PndGuidGenerator -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
</dependencies>


<build>
    <finalName>PndGuidRequestWs</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId> 
            <configuration>
                  <executable>true</executable>
            </configuration> 
        </plugin>
    </plugins>      
</build>
</project>

The PatentListWrapper class is:

package com.clarivate.pndguidrequestws.model;

import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class PatentListWrapper {

private List<PatentDetails> patentList;

public PatentListWrapper() {}

public List<PatentDetails> getPatentList() {
    return patentList;
}

public void setPatentList(List<PatentDetails> patentList) {
    this.patentList = patentList;
}   

}

Any suggestions most welcome.

EDIT: To simplify the object I created PatentListWrapper2 with just 1 string member:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class PatentListWrapper2 {

private String name;

public  PatentListWrapper2() {}

public  PatentListWrapper2(String name) {
    this.name = name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

I can again successfully send this xml using the ARC client:

<patentListWrapper2>
   <name>DSDS</name>
</patentListWrapper2>

with contentType="application/xml" but when I try to send patentListWrapper2 from java I get an unmarshalling error:

 2018-12-20 09:17:13,931 [main] INFO  - Running MainWsClient with name = DS fileType = post4
2018-12-20 09:17:14,166 [main] DEBUG - Created POST request for "http://localhost:8082/guidRequest/xmlList2"
2018-12-20 09:17:14,200 [main] DEBUG - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
2018-12-20 09:17:14,206 [main] DEBUG - Writing [com.springservice.client.PatentListWrapper2@517cd4b] using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@6cc7b4de]
2018-12-20 09:17:14,246 [main] DEBUG - POST request for "http://localhost:8082/guidRequest/xmlList2" resulted in 200 (null)
2018-12-20 09:17:14,248 [main] DEBUG - Reading [com.springservice.client.PatentListWrapper2] as "application/xml;charset=UTF-8" using [org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@6cc7b4de]
2018-12-20 09:17:14,255 [main] ERROR - DS2B org.springframework.web.client.RestClientException: Error while extracting response for type [class com.springservice.client.PatentListWrapper2] and content type [application/xml;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class com.springservice.client.PatentListWrapper2]: unexpected element (uri:"", local:"PatentListWrapper2"). Expected elements are <{}patentListWrapper2>; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"PatentListWrapper2"). Expected elements are <{}patentListWrapper2>

EDIT2 I ran pndGuidRequestWs on Eclipse Tomcat , instead of - Run As -> Spring Boot App. The server log is below:

2018-12-20 11:15:45.655  WARN 236 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.clarivate.pndguidrequestws.model.PatentDetails` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CN'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.clarivate.pndguidrequestws.model.PatentDetails` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CN') at [Source: (PushbackInputStream); line: 1, column: 98] (through reference chain: com.clarivate.pndguidrequestws.model.PatentListWrapper["patentList"]->java.util.ArrayList[0])         
DS.
  • 604
  • 2
  • 6
  • 24
  • sorry, but investigating the log you get a "400" not a "404"!? (400 means "bad request" ...) – xerx593 Dec 19 '18 at 12:32
  • @xerx593 sorry that was a typo. The error is 400 as you say, i've corrected the post – DS. Dec 19 '18 at 13:02
  • Since the endpoint produces `application/xml` and the `restTemplate` parses `application/json` there may be an issue here - can you try posting using `String.class` instead of `PatentListWrapper.class`? You may have to parse the XML string manualy afterwards. See https://stackoverflow.com/questions/12184731/spring-resttemplate-post-method-for-xml-response `String response = restTemplate.postForObject( url, patentListWrapper, String.class);` – Kali Dec 19 '18 at 13:32
  • @Quali, Thanks, I tried that but no luck, same 400 error – DS. Dec 19 '18 at 14:00
  • @Quali my bad, yes that works, but it sort of defeats the object of sending and receiving an object – DS. Dec 19 '18 at 14:54
  • It may help to annotate your `PatientListWrapper` with `javax.xml` annotations and let spring to the job. Example here: http://javainsimpleway.com/spring-resttemplate-crud-operations-with-xml/ I have never used `RestTemplate` for non json data, so unfortunately I can not help you with that. – Kali Dec 19 '18 at 14:59
  • Or alternatively just return json instead of xml - if the server side is within your control – Kali Dec 19 '18 at 15:00
  • @Quali I'd already annotated PatentListWrapper with '@XmlRootElement'. I've added the class at the end of the post for clarity. Both sides are within my control but we always use xml here, never use json. – DS. Dec 19 '18 at 15:18
  • np @DS. ;) ...regarding your "EDIT", the second log: *this problem* MUST be in the naming of the element, are you *sure*, you use `patentListWrapper2` (**2!**)? (in your code...since with the ARC client/browser, you confirmed) ... and the 400 still "strongly smells" like "bad headers"! (google "bad headers" you ll get 400;) – xerx593 Dec 20 '18 at 10:04
  • ...sry, its not the `2` but definitely some confusion about `p` vs. `P` (... atentListWrapper) – xerx593 Dec 20 '18 at 10:09
  • `unexpected element (uri:"", local:"PatentListWrapper2"). Expected elements are <{}patentListWrapper2>;` ...you can control "name" and "uri" of the xml by the according parameters of @XmlRootElement. – xerx593 Dec 20 '18 at 10:11
  • 1
    @xerx593 You're correct. I changed \@XmlRootElement -> @XmlRootElement(name="PatentListWrapper2") in PatentListWrapper2 and it worked. But , when I did the same change in PatentListWrapper it made no difference, still 400 error :( – DS. Dec 20 '18 at 10:41
  • ...can you get us some/more of the "server log"? (we seem to see the "client log" only ... https://stackoverflow.com/q/20485059/592355) – xerx593 Dec 20 '18 at 10:55
  • 1
    @xerx593 i've done that and updated the post with EDIT2. Why do I have a JSON parser error, when i'm using xml parsing? – DS. Dec 20 '18 at 11:21
  • you *should* specify "Content" header .... which could also result in error, but with an other message/converter hopefully. – xerx593 Dec 20 '18 at 11:52
  • @xerx593 I retried all 3 commented out sections of code in the Java client above. They all gave the same server err as posted in EDIT2. Do you know of any other ways to set the content header. – DS. Dec 20 '18 at 12:29

2 Answers2

1

Can you test with :

try {
        HttpHeaders headers = new HttpHeaders();
        //headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        //headers.setContentType((MediaType.APPLICATION_JSON));
        // I comment the code abouve because you did not specify a consumes whitch  defines the media types that the methods of 
        //a resource class or MessageBodyReader can accept. If not specified, a container will assume that any media type is acceptable.
        HttpEntity<PatentListWrapper> request = new HttpEntity<>(patentListWrapper, headers);
        PatentListWrapper patentListWrapperResult =  = restTemplate.exchange(url, HttpMethod.POST, request,PatentListWrapper.class);


        logger.debug("DS1A employee obj returned. guid = " +  patentListWrapper.getPatentList().get(0).getGuid());
    }catch(Exception e) {
        logger.error("DS1B " + e);      
    } 
Abder KRIMA
  • 3,418
  • 5
  • 31
  • 54
  • Thanks, I tried your suggestion but it still gives 400 error. Note I had to use : ResponseEntity patentListWrapperResult = ... – DS. Dec 19 '18 at 16:28
  • ok you said that with a rest client it work but not with RestTemplate? and the url of you service is http://localhost:8082/guidRequest/xmlList – Abder KRIMA Dec 19 '18 at 18:26
  • I think if you check the stack of your rest service you are going to find an error like : Cannot deserialize instance of `com.clarivate.pndguidrequestws.model.PatentListWrapper` out of START_ARRAY token !! – Abder KRIMA Dec 19 '18 at 23:53
  • The stack trace is in the post, are you saying I can get a different stack trace for the rest service, if so, how do I do that. Note, i've updated the post with an EDIT at the end. – DS. Dec 20 '18 at 09:55
0

PatentListWrapper is a complex object, not a piece of xml , so the answer is to remove all references to xml ie

  1. Remove @XmlRootElement(name="PatentListWrapper") from PatentListWrapper.
  2. Add jackson-*.jar(s) into the classpath to do the message converting
  3. Change the server side @RequestMapping from :

    @RequestMapping(value = "/xmlList", method = RequestMethod.POST , consumes = { "application/xml" }, produces = { "application/xml" }) 
    

to

@RequestMapping(value = "/xmlList", method = RequestMethod.POST )

This means that the ARC client now returns JSON (as it's the default return type), even when I send xml, but that's not important as it's just a test tool.

So, when posting objects with RestTemplate in Spring 2, no contentType settings or additional messageConverters are required on the client side , just:

RestTemplate restTemplate = new RestTemplate();
MyObject myObjectReturn = restTemplate.postForObject(url,myObject,MyObject.class);

and on the server side:

@RestController
@RequestMapping(value = "/endPoint", method = RequestMethod.POST)
public MyObject anyMethodName(@RequestBody  MyObject myObject) {
     //Do stuff to myObject
     return myObject;
}
DS.
  • 604
  • 2
  • 6
  • 24