5

I have this endpoint for Spring Rest API:

@PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE,
            MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
                    MediaType.APPLICATION_JSON_VALUE })
    public PaymentResponse handleMessage(@RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception {

    // get here plain XML  

}

XML model.

@XmlRootElement(name = "payment_transaction")
@XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTransaction {
    public enum Response {
        failed_response, successful_response
    }

    @XmlElement(name = "transaction_type")
    public String transactionType;
    .........
}

How I can get the XML request in plain XML text?

I also tried with Spring interceptor: I tried this code:

@SpringBootApplication
@EntityScan("org.plugin.entity")
public class Application extends SpringBootServletInitializer implements WebMvcConfigurer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    ........

    @Bean
    public RestTemplate rsestTemplate() {
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        RestTemplate restTemplate = new RestTemplate(
                new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    } 
}

Component for logging:

@Component
public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        StringBuilder sb = new StringBuilder();
        sb.append("[ ");
        for (byte b : body) {
            sb.append(String.format("0x%02X ", b));
        }
        sb.append("]");

        System.out.println("!!!!!!!!!!!!!!!");
        System.out.println(sb.toString());      

        ClientHttpResponse response = execution.execute(request, body);

        InputStream inputStream = response.getBody();

        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

        System.out.println("!!!!!!!!!!!!!!!");
        System.out.println(result);

        return response;
    }
}

But nothing is printed into the console. Any idea where I'm wrong? Probably this component is not registered?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808
  • I think your interceptor is not working correctly. Anyway, i think it's a little bit overkill to use an interceptor for logging. Are you sure this is what you want ? – Andrei Ciobanu Nov 25 '18 at 18:26
  • Do you actually want the request to be plain text or do you simply want to cast or otherwise convert the xml? If you want to receive in plain text you aren't going to be able to parae the XML or use any of the convention over configuration benefits of using XML without receiving it as text, then converting it to XML, then back to text. I'm asking just to clarify. Also can you provide a sample request – Chris Maggiulli Nov 25 '18 at 18:27
  • @ChrisMaggiulli I want to log everything. Not only XML, also request attempts. Can you give some advice how to implement it? – Peter Penzov Nov 25 '18 at 19:28
  • Have you looked at [this older](https://stackoverflow.com/questions/33744875/spring-boot-how-to-log-all-requests-and-responses-with-exceptions-in-single-pl/43155103#43155103) answer? It looks like it answers nearly the same question. – DavidW Nov 28 '18 at 18:51
  • You forgot to add the intercepter to `interceptors` `list` – Shiva Dec 02 '18 at 18:07

5 Answers5

3

Shouldn't it be easy like below to get it from HttpServletRequest, unless I'm missing something. I don't think there is need to use interceptor etc.

@PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE,
            MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
                    MediaType.APPLICATION_JSON_VALUE })
    public PaymentResponse handleMessage(HttpServletRequest request) throws Exception {

    String str, wholeXML = "";
    try {
        BufferedReader br = request.getReader();
        while ((str = br.readLine()) != null) {
            wholeXML += str;
        }
    System.out.println(wholeXML);
    //Here goes comment question, to convert it into PaymentTransaction
   JAXBContext jaxbContext = JAXBContext.newInstance(PaymentTransaction.class);
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

    StringReader reader = new StringReader(wholeXML);
    PaymentTransaction paymentTransaction = (PaymentTransaction) unmarshaller.unmarshal(reader);
}
Red Boy
  • 5,429
  • 3
  • 28
  • 41
  • But I get java.lang.IllegalStateException: UT010004: Cannot call getReader(), getInputStream() already called – Peter Penzov Nov 25 '18 at 19:19
  • @PeterPenzov Sorry, my bad, I just tested, you need to remove `@RequestBody PaymentTransaction transaction` from signature. – Red Boy Nov 25 '18 at 19:47
  • 1
    Yes but I would like to get also the XML object PaymentTransaction. Here I need to get the XML object and also in separate String variable the request content. Probably I need to use Interceptor? – Peter Penzov Nov 25 '18 at 19:50
  • From XML, you could get the `PaymentTransaction` as well, but here Spring won't help you directly with annotation. Even you want, I could show you, how to convert the XML to `PaymentTransaction` Object? – Red Boy Nov 25 '18 at 19:59
  • can you show me first how to implement properly ClientHttpRequestInterceptor? – Peter Penzov Nov 25 '18 at 20:00
  • @PeterPenzov I'm sorry, I have never implemented that, so not aware. In opinion for your use, I don't think its needed but it your call, you could wait for others to answer. Happy coding. – Red Boy Nov 25 '18 at 20:05
  • 1
    ok, can you show me how to convert the PaymentTransaction object to XML? – Peter Penzov Nov 25 '18 at 20:06
  • I get java.lang.IllegalStateException: UT010004: Cannot call getReader(), getInputStream() already called – Peter Penzov Jan 16 '19 at 07:18
  • I think it was your very first comment and that was resolved in current code snippet. – Red Boy Jan 16 '19 at 08:50
0

We had the same issue and use this solution in production. Which is not framework dependent (always an upside in my book) and simple.

Just consume it without specifying it as an XML. Then read the request lines and join them by \n if you want to have new lines in your xml. If not, join them by "" or whatever you please. This presumes you are using the javax.servlet.http.HttpServletRequest

Example:

@PostMapping(value = "/v1")
    public PaymentResponse handleMessage(HttpServletRequest request) throws Exception {

    final InputStream xml = request.getInputStream();
    final String xmlString = new BufferedReader(new InputStreamReader(xml))
          .lines()
          .collect(Collectors.joining("\n"));
   // do whatever you please with it

}

And you have an plain xml string.

Urosh T.
  • 3,336
  • 5
  • 34
  • 42
0

For your controller to receive the request body as a plain xml string, you need only change the @RequestBody parameter type to String:

@PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(@RequestBody String xmlOrJson, HttpServletRequest request) throws Exception {
    ...

With the above mapping, if the client has submitted xml, you'll see the raw XML. Otherwise, if the client has submitted json, you'll see the raw JSON. Make sure you check the request's "Content-Type" header to know which type you're dealing with.

See https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestbody

0

We've been using the spring-mvc-logger in production for quite a while. It's written as a servlet filter, so can be added as an independent wrapper to the MVC endpoint.

Our set up is almost exactly like described on the readme.md there, though we restrict the <url-pattern> under the <filter-mapping> to just the useful endpoints.

Even if it's not exactly what you're after, the codebase there makes quite a nice small example. In particular note the request/response wrapping that is needed in the filter. (This is to avoid the IllegalStateException: getReader(), getInputStream() already called that would otherwise happen if getReader() were called twice).

df778899
  • 10,703
  • 1
  • 24
  • 36
0

You have created List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(); but did not add the RestTemplateHeaderModifierInterceptor object to it.

You can autowire in the same in Application like below:

@Autowired 
ClientHttpRequestInterceptor clientHttpRequestInterceptor;

and

interceptors.add(clientHttpRequestInterceptor);

The code looks like below:

class Application {
...
@Autowired 
ClientHttpRequestInterceptor clientHttpRequestInterceptor;
@Bean
    public RestTemplate rsestTemplate() {
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        RestTemplate restTemplate = new RestTemplate(
                new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
interceptors.add(clientHttpRequestInterceptor);
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    } 
 ...
}

Hope it helps

Shiva
  • 1,962
  • 2
  • 13
  • 31