Skip to content

Commit dc97645

Browse files
authored
BAEL-9458: Sending XML POST Requests with Spring RestTemplate (#18902)
* BAEL-9458: Sending XML POST Requests with Spring RestTemplate * BAEL-9458: Sending XML POST Requests with Spring RestTemplate * BAEL-9458: Sending XML POST Requests with Spring RestTemplate * BAEL-9458: Sending XML POST Requests with Spring RestTemplate
1 parent 6c85743 commit dc97645

File tree

7 files changed

+279
-8
lines changed

7 files changed

+279
-8
lines changed

spring-web-modules/spring-resttemplate-3/pom.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<project xmlns="http://maven.apache.org/POM/4.0.0"
3-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
55
<modelVersion>4.0.0</modelVersion>
66
<artifactId>spring-resttemplate-3</artifactId>
77
<version>0.1-SNAPSHOT</version>
@@ -63,6 +63,12 @@
6363
<artifactId>commons-io</artifactId>
6464
<version>${commons-io.version}</version>
6565
</dependency>
66+
67+
<!-- XML support using Jackson -->
68+
<dependency>
69+
<groupId>com.fasterxml.jackson.dataformat</groupId>
70+
<artifactId>jackson-dataformat-xml</artifactId>
71+
</dependency>
6672
</dependencies>
6773

6874
<properties>

spring-web-modules/spring-resttemplate-3/src/main/java/com/baeldung/compress/RestTemplateConfiguration.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@
22

33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Primary;
6+
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
57
import org.springframework.web.client.RestTemplate;
68

9+
import java.util.stream.Collectors;
10+
711
@Configuration
812
public class RestTemplateConfiguration {
913

10-
/**
11-
* A RestTemplate that compresses requests.
12-
*
13-
* @return RestTemplate
14-
*/
1514
@Bean
16-
public RestTemplate getRestTemplate() {
15+
@Primary
16+
public RestTemplate restTemplate() {
1717
RestTemplate restTemplate = new RestTemplate();
18+
19+
// Remove XML converters to ensure JSON is used
20+
restTemplate.setMessageConverters(
21+
restTemplate.getMessageConverters().stream()
22+
.filter(converter -> !(converter instanceof MappingJackson2XmlHttpMessageConverter))
23+
.collect(Collectors.toList())
24+
);
25+
1826
restTemplate.getInterceptors().add(new CompressingClientHttpRequestInterceptor());
1927
return restTemplate;
2028
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.baeldung.xmlpost.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.client.RestTemplate;
6+
7+
@Configuration
8+
public class RestTemplateConfig {
9+
@Bean("xmlRestTemplate")
10+
public RestTemplate xmlRestTemplate() {
11+
return new RestTemplate();
12+
}
13+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.baeldung.xmlpost.model;
2+
3+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
4+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
5+
6+
@JacksonXmlRootElement(localName = "PaymentRequest")
7+
public class PaymentRequest {
8+
9+
@JacksonXmlProperty(localName = "transactionId")
10+
private String transactionId;
11+
12+
@JacksonXmlProperty(localName = "amount")
13+
private Double amount;
14+
15+
@JacksonXmlProperty(localName = "currency")
16+
private String currency;
17+
18+
@JacksonXmlProperty(localName = "recipient")
19+
private String recipient;
20+
21+
public PaymentRequest() {
22+
}
23+
24+
public PaymentRequest(String transactionId, Double amount, String currency, String recipient) {
25+
this.transactionId = transactionId;
26+
this.amount = amount;
27+
this.currency = currency;
28+
this.recipient = recipient;
29+
}
30+
31+
public String getTransactionId() {
32+
return transactionId;
33+
}
34+
35+
public void setTransactionId(String transactionId) {
36+
this.transactionId = transactionId;
37+
}
38+
39+
public Double getAmount() {
40+
return amount;
41+
}
42+
43+
public void setAmount(Double amount) {
44+
this.amount = amount;
45+
}
46+
47+
public String getCurrency() {
48+
return currency;
49+
}
50+
51+
public void setCurrency(String currency) {
52+
this.currency = currency;
53+
}
54+
55+
public String getRecipient() {
56+
return recipient;
57+
}
58+
59+
public void setRecipient(String recipient) {
60+
this.recipient = recipient;
61+
}
62+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.baeldung.xmlpost.model;
2+
3+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
4+
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
5+
6+
@JacksonXmlRootElement(localName = "PaymentResponse")
7+
public class PaymentResponse {
8+
9+
@JacksonXmlProperty(localName = "status")
10+
private String status;
11+
12+
@JacksonXmlProperty(localName = "message")
13+
private String message;
14+
15+
@JacksonXmlProperty(localName = "referenceNumber")
16+
private String referenceNumber;
17+
18+
public PaymentResponse() {
19+
}
20+
21+
public PaymentResponse(String status, String message, String referenceNumber) {
22+
this.status = status;
23+
this.message = message;
24+
this.referenceNumber = referenceNumber;
25+
}
26+
27+
public String getStatus() {
28+
return status;
29+
}
30+
31+
public void setStatus(String status) {
32+
this.status = status;
33+
}
34+
35+
public String getMessage() {
36+
return message;
37+
}
38+
39+
public void setMessage(String message) {
40+
this.message = message;
41+
}
42+
43+
public String getReferenceNumber() {
44+
return referenceNumber;
45+
}
46+
47+
public void setReferenceNumber(String referenceNumber) {
48+
this.referenceNumber = referenceNumber;
49+
}
50+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.baeldung.xmlpost.service;
2+
3+
import com.baeldung.xmlpost.model.PaymentRequest;
4+
import com.baeldung.xmlpost.model.PaymentResponse;
5+
import org.springframework.beans.factory.annotation.Qualifier;
6+
import org.springframework.http.HttpEntity;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.http.MediaType;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.web.client.RestTemplate;
12+
13+
import java.util.Collections;
14+
15+
@Service
16+
public class PaymentService {
17+
private final RestTemplate restTemplate;
18+
19+
public PaymentService(@Qualifier("xmlRestTemplate") RestTemplate restTemplate) {
20+
this.restTemplate = restTemplate;
21+
}
22+
23+
public PaymentResponse processPayment(PaymentRequest request, String paymentUrl) {
24+
try {
25+
HttpHeaders headers = new HttpHeaders();
26+
headers.setContentType(MediaType.APPLICATION_XML);
27+
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
28+
29+
HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
30+
31+
ResponseEntity<PaymentResponse> response =
32+
restTemplate.postForEntity(paymentUrl, entity, PaymentResponse.class);
33+
34+
return response.getBody();
35+
} catch (Exception ex) {
36+
throw new RuntimeException("Payment processing failed: " + ex.getMessage(), ex);
37+
}
38+
}
39+
}
40+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.baeldung.xmlpost.service;
2+
3+
import com.baeldung.xmlpost.model.PaymentRequest;
4+
import com.baeldung.xmlpost.model.PaymentResponse;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.ExtendWith;
7+
import org.mockito.InjectMocks;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
import org.springframework.http.HttpEntity;
11+
import org.springframework.http.HttpStatus;
12+
import org.springframework.http.MediaType;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.web.client.HttpClientErrorException;
15+
import org.springframework.web.client.RestTemplate;
16+
17+
import static org.junit.jupiter.api.Assertions.*;
18+
import static org.mockito.ArgumentMatchers.*;
19+
import static org.mockito.Mockito.verify;
20+
import static org.mockito.Mockito.when;
21+
22+
@ExtendWith(MockitoExtension.class)
23+
class PaymentServiceUnitTest {
24+
25+
@Mock
26+
private RestTemplate restTemplate;
27+
28+
@InjectMocks
29+
private PaymentService paymentService;
30+
31+
private final String testUrl = "http://mock-payment-service";
32+
33+
@Test
34+
void givenValidPaymentRequest_whenProcessPayment_thenReturnSuccessfulResponse() {
35+
PaymentRequest request = new PaymentRequest("TXN001", 100.50, "USD", "Jane Doe");
36+
PaymentResponse expectedResponse = new PaymentResponse(
37+
"SUCCESS", "Payment processed successfully", "REF12345"
38+
);
39+
40+
ResponseEntity<PaymentResponse> mockResponse =
41+
new ResponseEntity<>(expectedResponse, HttpStatus.OK);
42+
43+
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
44+
.thenReturn(mockResponse);
45+
46+
PaymentResponse actualResponse = paymentService.processPayment(request, testUrl);
47+
48+
assertNotNull(actualResponse);
49+
assertEquals("SUCCESS", actualResponse.getStatus());
50+
assertEquals("REF12345", actualResponse.getReferenceNumber());
51+
assertEquals("Payment processed successfully", actualResponse.getMessage());
52+
53+
verify(restTemplate).postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class));
54+
}
55+
56+
@Test
57+
void givenRemoteServiceReturnsBadRequest_whenProcessPayment_thenThrowMeaningfulException() {
58+
PaymentRequest request = new PaymentRequest("TXN002", 200.0, "EUR", "John Smith");
59+
60+
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
61+
.thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Invalid amount"));
62+
63+
RuntimeException exception = assertThrows(RuntimeException.class,
64+
() -> paymentService.processPayment(request, testUrl));
65+
66+
assertTrue(exception.getMessage().contains("Payment processing failed"));
67+
assertTrue(exception.getMessage().contains("Invalid amount"));
68+
}
69+
70+
@Test
71+
void givenXmlRequest_whenProcessPayment_thenSetCorrectXmlHttpHeaders() {
72+
PaymentRequest request = new PaymentRequest("TXN004", 300.0, "CAD", "Bob Wilson");
73+
PaymentResponse expectedResponse = new PaymentResponse("SUCCESS", "OK", "REF67890");
74+
75+
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(PaymentResponse.class)))
76+
.thenReturn(new ResponseEntity<>(expectedResponse, HttpStatus.OK));
77+
78+
paymentService.processPayment(request, testUrl);
79+
80+
verify(restTemplate).postForEntity(
81+
eq(testUrl),
82+
argThat((HttpEntity<PaymentRequest> entity) -> {
83+
boolean hasXmlContentType = entity.getHeaders().getContentType()
84+
.includes(MediaType.APPLICATION_XML);
85+
boolean acceptsXml = entity.getHeaders().getAccept()
86+
.contains(MediaType.APPLICATION_XML);
87+
return hasXmlContentType && acceptsXml;
88+
}),
89+
eq(PaymentResponse.class)
90+
);
91+
}
92+
}

0 commit comments

Comments
 (0)