How can I test a my rest controller that has feign clients? - spring

I have a rest controller that is using 2 fein clients, I want to write and test the Rest controller with different samples, I am not an expert at writing springboot tests.
In this scenario I have no repositories to test with, just feign clients accessed through a rest controller. Below is my testing controller code
#RestController
public class CustomerController {
#Autowired
private CustomerClient customerClient;
#Autowired
private PaymentsClient paymentsClient;
#RequestMapping(path = "/getAllCustomers", method = RequestMethod.GET)
public ResponseEntity<Object> getAllCustomers() {
List<Customer> customers = customerClient.getAllCustomers();
return new ResponseEntity<>(customers, HttpStatus.OK);
}
#RequestMapping(path = "/{customerId}", method = RequestMethod.GET)
public ResponseEntity<Object> get(#PathVariable() long customerId) {
try {
Customer c = customerClient.getCustomerById(customerId);
if (c != null) {
return new ResponseEntity<>(c, HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Customer Not Found");
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "/{customerId}", method = RequestMethod.PATCH)
public ResponseEntity<Object> UpdateCustomer(#PathVariable() Long customerId, #RequestBody Customer customer) {
Customer c;
try {
c = customerClient.update(customerId, customer);
if (c != null) {
return new ResponseEntity<>(c, HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Customer Not Found");
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "", method = RequestMethod.POST)
public ResponseEntity<Object> saveCustomer(#RequestBody Customer customer) {
Customer c;
try {
c = customerClient.saveCustomer(customer);
return new ResponseEntity<>(c, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "/registerPayment", method = RequestMethod.POST)
public ResponseEntity<Object> saveCustomer(#RequestBody Payment payment) {
Payment p = null;
Customer c = null;
try {
c = customerClient.getCustomerById(payment.getCustomerId());
p = paymentsClient.saveCustomer(payment);
return new ResponseEntity<>(p, HttpStatus.OK);
} catch (Exception e) {
if (null == c) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("Customer Does not Exist");
} else {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
}
Most of the tests I have seen have a repository injected in but for my case
I dont have those, just fein clients, Im I doing it wrong,
Below is my current test
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class CustomerControllerTest {
#Autowired
private MockMvc mockMvc;
#InjectMocks
private CustomerController customerController;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(customerController).build();
}
#Test
public void getAllCustomers() {
try {
this.mockMvc.perform(get("/getAllCustomers")).andExpect(status().isOk())
.andExpect(content().json("[{\n" + " \"customerId\": 24,\n"
+ " \"firstName\": \"Benjamin\",\n" + " \"secondName\": \" Masiga\",\n"
+ " \"email\": \"ben#ben.com\"\n" + " }"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I'm getting the error below,
NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate.

Its as simple as writing any other JUnit test.
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class CustomerControllerTest {
#Mock
private CustomerClient customerClient;
#InjectMocks
private CustomerController customerController;
#Test
public void getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers.add(new Customers("name"));
Mockito.when(customerClient.getAllCustomers()).thenReturn(customers);
Mockito.assertEquals(customers.toString(),customerController.getAllCustomers())
}
}

You need your own feign.Client that works beyond MockMvc:
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.function.Function.identity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
import feign.Client;
import feign.Request;
import feign.Response;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
public class MockMvcFeignClient implements Client {
private final MockMvc mockMvc;
public MockMvcFeignClient(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
#Override
public Response execute(Request request, Request.Options options) {
URI requestUrl = URI.create(request.url());
List<String> uriVars = new ArrayList<>();
String urlTemplate = fillVarsAndGetUrlTemplate(requestUrl, uriVars);
HttpMethod method = HttpMethod.valueOf(request.method());
byte[] body = request.body();
HttpHeaders httpHeaders = convertHeaders(request);
MockHttpServletRequestBuilder requestBuilder = request(method, urlTemplate, uriVars.toArray())
.headers(httpHeaders)
.content(body);
MockHttpServletResponse resp;
try {
ResultActions resultActions = mockMvc.perform(requestBuilder);
resp = resultActions.andReturn()
.getResponse();
} catch (Exception e) {
throw new IllegalStateException("Error while executing request", e);
}
return convertResponse(request, resp);
}
static String fillVarsAndGetUrlTemplate(URI requestUrl, List<String> uriVars) {
StringBuilder urlTemplate = new StringBuilder(requestUrl.getPath());
if (requestUrl.getQuery() != null) {
urlTemplate.append('?');
String[] pairs = requestUrl.getRawQuery().split("&");
for (int i = 0; i < pairs.length; i++) {
String pair = pairs[i];
int separator = pair.indexOf('=');
String paramName;
String paramValue;
if (separator < 0) {
paramName = pair;
paramValue = null;
} else {
paramName = pair.substring(0, separator);
try {
paramValue = URLDecoder.decode(pair.substring(separator + 1), UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new UncheckedIOException(e);
}
}
urlTemplate.append(i == 0 ? paramName : "&" + paramName);
if (paramValue != null) {
urlTemplate.append("={").append(paramName).append('}');
uriVars.add(paramValue);
}
}
}
return urlTemplate.toString();
}
private static HttpHeaders convertHeaders(Request request) {
HttpHeaders headers = new HttpHeaders();
request.headers().forEach((header, values) -> headers.put(header, new ArrayList<>(values)));
return headers;
}
private static Response convertResponse(Request request, MockHttpServletResponse resp) {
return Response.builder()
.request(request)
.status(resp.getStatus())
.body(resp.getContentAsByteArray())
.headers(resp.getHeaderNames().stream()
.collect(Collectors.toMap(identity(), resp::getHeaders)))
.build();
}
}
and create your feign client with it:
return Feign.builder()
.client(new MockMvcFeignClient(mockMvc))
...
So for tests where you could make MockMvc integration tests, now you can call it via feign client.

Related

#Retryable is not working when calling from a method

Below is my application class. The flow is like the DEToken class from here and from DEToken I call RestConnection where I have the #retryable method.
#SpringBootApplication
#EnableRetry
public class SpringBootTrfficApplication implements CommandLineRunner {
Enter code here
#Autowired
DEToken deToken;
#Autowired
SyncService syncService;
public static void main(String[] args) {
SpringApplication.run(SpringBootTrfficApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
deToken.getToken();
}
}
DEToken class: from getToken I am calling RestConnect where I have the #Retrable method:
#Service
public class DEToken {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
RestConnection restConnection;
#Autowired
private Environment env;
public String accessToken;
public void getToken() {
System.out.println("hello from get token");
//String getJsonPayload = "{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
//env.getProperty("provider.ins") + "}}}";
//String str = restConnection.restPost(
// env.getProperty("rest.de.url"), getJsonPayload);
try {
String getJsonPayload =
"{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
env.getProperty("provider.ins") + "}}}";
StringBuffer tokenResult =
restConnection.restPost(env.getProperty("rest.de.url"),
getJsonPayload);
System.out.println(tokenResult);
JSONObject xmlJSONObj = XML.toJSONObject(tokenResult.toString());
JSONObject registration = new JSONObject();
if (xmlJSONObj.has("Registration")) {
registration = xmlJSONObj.getJSONObject("Registration");
if (registration.has("accessToken")) {
accessToken = registration.get("accessToken").toString();
}
else
logger.info("no accessToken from DE");
}
else
logger.info("no Registration object from DE");
}
catch (Exception e) {
logger.error("Exception while fetching accesstoken from DE ");
logger.error(e.getMessage());
}
}
}
My REST connection class where I have retryable method:
#Service
public class RestConnection {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
private Environment env;
public void setBaseUrl(String value, String ip) {
//baseUrl = value;
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> hostname.equals(ip));
}
/*
* REST post call
*/
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
StringBuffer sb = new StringBuffer();
HttpURLConnection conn = null;
try {
URL url = new URL(restUrl);
String protocol = url.getProtocol();
if (protocol.toLowerCase().equals("http")) {
conn = (HttpURLConnection)url.openConnection();
}
else if (protocol.toLowerCase().equals("https")) {
//setTrustedCert();
conn = (HttpsURLConnection)url.openConnection();
}
else {
logger.info("Protocol is neither HTTP nor HTTPS");
}
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("version", env.getProperty("de.version"));
conn.setRequestProperty("accessToken", env.getProperty("access.token"));
conn.setRequestProperty("requestHost", env.getProperty("server.de.host"));
conn.setRequestProperty("requestPort", env.getProperty("server.port"));
conn.setRequestProperty("requestProtocol",
env.getProperty("server.de.protocol"));
PrintWriter pout =
new PrintWriter(
new OutputStreamWriter(
conn.getOutputStream(), "UTF-8"),
true);
pout.print(payload);
pout.flush();
pout.close();
InputStream isi = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(isi);
int numCharsRead1;
char[] charArray1 = new char[1024];
while ((numCharsRead1 = isr.read(charArray1)) > 0) {
sb.append(charArray1, 0, numCharsRead1);
}
isr.close();
isi.close();
}
catch (MalformedURLException e) {
logger.error("MalformedURLException in restAccessTokenPOST..." +
e.getMessage());
//e.printStackTrace();
}
catch (IOException e) {
logger.error("IOException in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
catch (Exception e) {
logger.error("Exception in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
finally {
if (null != conn)
conn.disconnect();
}
return sb;
}
#Recover
public String helpHere(ConnectException cause) {
System.out.println("Recovery place! ConnectException");
return "Hello";
}
#Recover
public String helpHere(IOException cause) {
System.out.println("Recovery place! ArithmeticException");
return "Hello";
}
#Recover
public String helpHere(Exception cause) {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere() {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere(Throwable cause) {
System.out.println("Recovery place! Throwable");
return "Hello";
}
}
Considering you see your function restPost() implementation,
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
try {
// Your code
}
catch(IOException ex){ // These catch block handles the exception
// and nothing to throw to retryable.
}
catch(MalformedURLException ex){ // More catch blocks that you
// define to handle exception.
}
}
Here you handle all of the exceptions that can be a cause to revoke the retry and recover methods.
Note: Recoverable methods only execute when a exception is thrown, not handled by any try-catch block.
Whatever exception is raised by method restPost() is handled by the method try-catch block itself and there are no exceptions that had been rethrow by a catch block.
Now, Spring-Retry is unable to get any exception (because it is handled by the method try-catch block). So, no recovery method will be executed.
Solution: you should remove those catch blocks from the method definition on which you want to perform retry or recover.
Please do the needful and it will work like a charm... :)

Accept Strings and XML data with RestController

I want to create REST Server which accepts XML requests and plain text into different controllers. I tried to implement this:
#SpringBootApplication
public class Application extends SpringBootServletInitializer implements WebMvcConfigurer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
..............
private BasicAuthenticationInterceptor basicAuthenticationInterceptor;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2XmlHttpMessageConverter);
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(new MappingJackson2XmlHttpMessageConverter(
((XmlMapper) createObjectMapper(Jackson2ObjectMapperBuilder.xml()))
.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)));
converters.add(new MappingJackson2HttpMessageConverter(createObjectMapper(Jackson2ObjectMapperBuilder.json())));
}
private ObjectMapper createObjectMapper(Jackson2ObjectMapperBuilder builder) {
builder.indentOutput(true);
builder.modules(new JaxbAnnotationModule());
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.defaultUseWrapper(false);
return builder.build();
}
#Autowired
public void setBasicAuthenticationInterceptor(BasicAuthenticationInterceptor basicAuthenticationInterceptor) {
this.basicAuthenticationInterceptor = basicAuthenticationInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(basicAuthenticationInterceptor);
}
}
Check for XML proper formatting:
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
PaymentTransaction response;
if (ex.getMessage().contains("Required request body")) {
response = new PaymentTransaction(PaymentTransaction.Response.failed_response, 350,
"Invalid XML message: No XML data received", "XML request parsing failed!");
} else {
response = new PaymentTransaction(PaymentTransaction.Response.failed_response, 351,
"Invalid XML message format", null);
}
return ResponseEntity.badRequest().body(response);
}
}
Controller Class:
#RestController()
public class HomeController {
#Autowired
public HomeController(Map<String, MessageProcessor> processors, Map<String, ReconcileProcessor> reconcileProcessors,
#Qualifier("defaultProcessor") MessageProcessor defaultProcessor,
AuthenticationService authenticationService, ClientRepository repository,
#Value("${request.limit}") int requestLimit) {
// Here I receive XML
}
#GetMapping(value = "/v1/*")
public String message() {
return "REST server";
}
#PostMapping(value = "/v1/{token}", consumes = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(#PathVariable("token") String token,
#RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception {
// Here I receive XML
}
#PostMapping(value = "/v1/notification")
public ResponseEntity<String> handleNotifications(#RequestBody Map<String, String> keyValuePairs) {
// Here I receive key and value in request body
}
#PostMapping(value = "/v1/summary/by_date/{token}", consumes = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public PaymentResponses handleReconcile(#PathVariable("token") String token, #RequestBody Reconcile reconcile,
HttpServletRequest request) throws Exception {
// Here I receive XML
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public static class UnauthorizedException extends RuntimeException {
UnauthorizedException(String message) {
super(message);
}
}
}
As you can see in some methods I receive XML and in other I receive String in form of key=value&.....
How I configure Spring to accept both types?
Also should I split the Rest controller into different files?
EDIT:
Sample XML request:
<?xml version="1.0" encoding="UTF-8"?>
<payment_transaction>
<transaction_type>authorize</transaction_type>
<transaction_id>2aeke4geaclv7ml80</transaction_id>
<amount>1000</amount>
<currency>USD</currency>
<card_number>22</card_number>
<shipping_address>
<first_name>Name</first_name>
</shipping_address>
</payment_transaction>
Sample XML response:
<?xml version="1.0" encoding="UTF-8"?>
<payment_response>
<transaction_type>authorize</transaction_type>
<status>approved</status>
<unique_id>5f7edd36689f03324f3ef531beacfaae</unique_id>
<transaction_id>asdsdlddea4sdaasdsdsa4dadasda</transaction_id>
<code>500</code>
<amount>101</amount>
<currency>EUR</currency>
</payment_response>
Sample Notification request:
uniqueid=23434&type=sale&status=33
Sample Notification response: It should return only HTTP status OK.
I use:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>
Java version: "10.0.2" 2018-07-17
About the XML generation I use:
#XmlRootElement(name = "payment_transaction")
public class PaymentTransaction {
public enum Response {
failed_response, successful_response
}
#XmlElement(name = "transaction_type")
public String transactionType;
#XmlElement(name = "transaction_id")
public String transactionId;
#XmlElement(name = "usage")
POM Configuration: https://pastebin.com/zXqYhDH3
For Spring boot 2.0.4-RELEASE, it seems you don't have to do a lot.
I made this configuration:
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
FormHttpMessageConverter converter = new FormHttpMessageConverter();
//MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
//converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
converters.add(converter);
MappingJackson2HttpMessageConverter conv1 = new MappingJackson2HttpMessageConverter();
conv1.getObjectMapper().registerModule(new JaxbAnnotationModule());
converters.add(conv1);
MappingJackson2XmlHttpMessageConverter conv = new MappingJackson2XmlHttpMessageConverter();
// required by jaxb annotations
conv.getObjectMapper().registerModule(new JaxbAnnotationModule());
converters.add(conv);
}
}
I used about your DTO:
#XmlRootElement(name = "payment_transaction")
public class PaymentTransaction {
#XmlElement(name = "transaction_type")
public String transactionType;
#XmlElement(name = "transaction_id")
public String transactionId;
public String getTransactionType() {
return transactionType;
}
public void setTransactionType(String transactionType) {
this.transactionType = transactionType;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
#Override
public String toString() {
return "PaymentTransaction [transactionType=" + transactionType
+ ", transactionId=" + transactionId + "]";
}
}
The controller:
#RestController
public class MyController {
/**
* https://stackoverflow.com/questions/34782025/http-post-request-with-content-type-application-x-www-form-urlencoded-not-workin/38252762#38252762
*/
#PostMapping(value = "/v1/{token}",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody PaymentTransaction handleMessage(#PathVariable("token") String token,
#RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception {
System.out.println("handleXmlMessage");
System.out.println(transaction);
PaymentTransaction body = new PaymentTransaction();
body.setTransactionId(transaction.getTransactionId());
body.setTransactionType("received: " + transaction.getTransactionType());
return body;
}
#PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(#ModelAttribute PaymentTransaction transaction) {
System.out.println("handleFormMessage");
System.out.println(transaction);
return new ResponseEntity<String>(HttpStatus.OK);
}
}
The only main thing to remember that it seems the filling of the DTO with the parsed data happens by reflection:
For your input
<payment_transaction>
<transaction_id>1</transaction_id>
<transaction_type>name</transaction_type>
</payment_transaction>
I got this response (see my controller):
{
"transactionType": "received: null",
"transactionId": null
}
But when I changed to the name of the fields of the DTO, it started to work (the root element did not matter, interesting):
<payment_transaction>
<transactionId>1</transactionId>
<transactionType>name</transactionType>
</payment_transaction>
result:
{
"transactionType": "received: name",
"transactionId": "1"
}
The same is true for the querystring. I don't know what to change to get spring to parse the xmls using the defined names in #XmlRootElement/#XmlElement.
This is an another solution (it worked well for me) with less Spring magic and using the good old way of HttpServletRequestWrapper.
In the WebMvcConfigurerAdapter class, now we don't need the MessageConverter:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
//FormHttpMessageConverter converter = new FormHttpMessageConverter();
//MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
//converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
//converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
//converters.add(converter);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new MappingJackson2XmlHttpMessageConverter());
super.configureMessageConverters(converters);
}
And everything else happens in this (servlet) Filter implementation:
#WebFilter("/v1/notification")
public class MyRequestBodyFilter implements Filter {
private static class MyServletInputStream extends ServletInputStream {
private ByteArrayInputStream buffer;
public MyServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}
#Override
public int read() throws IOException {
return buffer.read();
}
#Override
public boolean isFinished() {
return buffer.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
private class MyHttpServletRequestWrapper extends HttpServletRequestWrapper{
MyHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
// converting the request parameters to the pojo and serialize it to XML
// the drawback of this way that the xml will be parsed again somewhere later
long id = Long.parseLong(getRequest().getParameter("id"));
String name = getRequest().getParameter("name");
MyRequestBody body = new MyRequestBody();
body.setId(id);
body.setName(name);
return new MyServletInputStream(new XmlMapper().writeValueAsBytes(body));
}
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
chain.doFilter(new MyHttpServletRequestWrapper(httpRequest), response);
}
#Override
public void destroy() {
}
}
I have changed nothing in my test controller, so the signature of the methods remained the same:
#PostMapping(value = "/v1/{token}",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody MyResponseBody handleMessage(#PathVariable("token") String token, #RequestBody MyRequestBody transaction, HttpServletRequest request) throws Exception {
MyResponseBody body = new MyResponseBody();
body.setId(transaction.getId());
body.setName("received " + transaction.getName());
return body;
}
#PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(#ModelAttribute MyRequestBody transaction) {
return new ResponseEntity<String>(HttpStatus.OK);
}
Update this solution works for pre-2.x Spring-boot versions. Another thing to consider that during my tests I used Jackson's XML annotations on my DTOs (JacksonXmlRootElement, JacksonXmlProperty) and maybe FormHttpMessageConverter can handle DTOs with standard JAXB annotations (see my answer for Spring 2.0.4-RELEASE) - so may you'd better to go to that direction if you can (or at least give it a try before you apply the sketched solution).
This is my solution. I dropped the RequestIntereptor (because that is rather for inspect the request not for modifying it) and the RequestBodyAdvice too (because it turned out that there is a better way.
If you have a look for the available MessageConverters you can see that the only MessageConverter that reads the posted form data is the FormHttpMessageConverter.
The problem with this class is the return type, which is Multivaluemap
But, using this class as a base, I have created an abstract class that reads the form data to this Multivaluemap, and have only one abstract funtion that you have to implement in the subclass: that will create an object from the values stored in the multivaluemap.
Unfortunately I had to introduce an interface (because I kept the original implementation of the writing part just adopt it) on the DTO you would like to read.
All in all, my working solution:
In the WebMvcConfigurerAdapter class, I have this config:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
//FormHttpMessageConverter converter = new FormHttpMessageConverter();
MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
//converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
converters.add(converter);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new MappingJackson2XmlHttpMessageConverter());
super.configureMessageConverters(converters);
}
I modified a bit your controller functions:
#PostMapping(value = "/v1/{token}",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody MyResponseBody handleMessage(#PathVariable("token") String token, #RequestBody MyRequestBody transaction, HttpServletRequest request) throws Exception {
MyResponseBody body = new MyResponseBody();
body.setId(transaction.getId());
body.setName("received " + transaction.getName());
return body;
}
// check #ModelAttribute workaround https://stackoverflow.com/questions/4339207/http-post-with-request-content-type-form-not-working-in-spring-mvc-3
#PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(#ModelAttribute MyRequestBody transaction) {
return new ResponseEntity<String>(HttpStatus.OK);
}
(in the next part the import packages are meaningful, some mail api classes can be found somewhere else)
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.mail.internet.MimeUtility;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* based on {#link org.springframework.http.converter.FormHttpMessageConverter
*
* it uses the readed MultiValueMap to build up the DTO we would like to get from the request body.
*/
public abstract class AbstractRequestBodyFormHttpMessageConverter<T extends RequestParamSupport> implements HttpMessageConverter<T> {
/**
* This is the only method you have to implement for your DTO class
* the class must implement RequestParamSupport
*/
protected abstract T buildObject(MultiValueMap<String, Object> valueMap);
public interface RequestParamSupport{
MultiValueMap<String, Object> getRequestParams();
}
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
private Charset charset = DEFAULT_CHARSET;
private Charset multipartCharset;
private Class<T> bodyClass;
public AbstractRequestBodyFormHttpMessageConverter(Class<T> bodyClass) {
this.bodyClass = bodyClass;
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
applyDefaultCharset();
}
/**
* Set the character set to use when writing multipart data to encode file
* names. Encoding is based on the encoded-word syntax defined in RFC 2047
* and relies on {#code MimeUtility} from "javax.mail".
* <p>If not set file names will be encoded as US-ASCII.
* #since 4.1.1
* #see Encoded-Word
*/
public void setMultipartCharset(Charset charset) {
this.multipartCharset = charset;
}
/**
* Apply the configured charset as a default to registered part converters.
*/
private void applyDefaultCharset() {
for (HttpMessageConverter<?> candidate : this.partConverters) {
if (candidate instanceof AbstractHttpMessageConverter) {
AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
// Only override default charset if the converter operates with a charset to begin with...
if (converter.getDefaultCharset() != null) {
converter.setDefaultCharset(this.charset);
}
}
}
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (!bodyClass.isAssignableFrom(clazz)) {
return false;
}
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
// We can't read multipart....
if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if (!bodyClass.isAssignableFrom(clazz)) {
return false;
}
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
/**
* Set the list of {#link MediaType} objects supported by this converter.
*/
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
this.supportedMediaTypes = supportedMediaTypes;
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.unmodifiableList(this.supportedMediaTypes);
}
#Override
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
MultiValueMap<String, Object> result = new LinkedMultiValueMap<String, Object>(pairs.length);
for (String pair : pairs) {
int idx = pair.indexOf('=');
if (idx == -1) {
result.add(URLDecoder.decode(pair, charset.name()), null);
}
else {
String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
result.add(name, value);
}
}
return buildObject(result);
}
#Override
public void write(T object, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
if (!isMultipart(object, contentType)) {
writeForm(object.getRequestParams(), contentType, outputMessage);
}
else {
writeMultipart(object.getRequestParams(), outputMessage);
}
}
private boolean isMultipart(RequestParamSupport object, MediaType contentType) {
if (contentType != null) {
return MediaType.MULTIPART_FORM_DATA.includes(contentType);
}
MultiValueMap<String, Object> map = object.getRequestParams();
for (String name : map.keySet()) {
for (Object value : map.get(name)) {
if (value != null && !(value instanceof String)) {
return true;
}
}
}
return false;
}
private void writeForm(MultiValueMap<String, Object> form, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException {
Charset charset;
if (contentType != null) {
outputMessage.getHeaders().setContentType(contentType);
charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
}
else {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
charset = this.charset;
}
StringBuilder builder = new StringBuilder();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
for (Iterator<Object> valueIterator = form.get(name).iterator(); valueIterator.hasNext();) {
String value = (String) valueIterator.next();
builder.append(URLEncoder.encode(name, charset.name()));
if (value != null) {
builder.append('=');
builder.append(URLEncoder.encode(value, charset.name()));
if (valueIterator.hasNext()) {
builder.append('&');
}
}
}
if (nameIterator.hasNext()) {
builder.append('&');
}
}
final byte[] bytes = builder.toString().getBytes(charset.name());
outputMessage.getHeaders().setContentLength(bytes.length);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
#Override
public void writeTo(OutputStream outputStream) throws IOException {
StreamUtils.copy(bytes, outputStream);
}
});
}
else {
StreamUtils.copy(bytes, outputMessage.getBody());
}
}
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException {
final byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
#Override
public void writeTo(OutputStream outputStream) throws IOException {
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
}
});
}
else {
writeParts(outputMessage.getBody(), parts, boundary);
writeEnd(outputMessage.getBody(), boundary);
}
}
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {
for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {
String name = entry.getKey();
for (Object part : entry.getValue()) {
if (part != null) {
writeBoundary(os, boundary);
writePart(name, getHttpEntity(part), os);
writeNewLine(os);
}
}
}
}
#SuppressWarnings("unchecked")
private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
Object partBody = partEntity.getBody();
Class<?> partType = partBody.getClass();
HttpHeaders partHeaders = partEntity.getHeaders();
MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters) {
if (messageConverter.canWrite(partType, partContentType)) {
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty()) {
multipartMessage.getHeaders().putAll(partHeaders);
}
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
return;
}
}
throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
"found for request type [" + partType.getName() + "]");
}
/**
* Generate a multipart boundary.
* <p>This implementation delegates to
* {#link MimeTypeUtils#generateMultipartBoundary()}.
*/
protected byte[] generateMultipartBoundary() {
return MimeTypeUtils.generateMultipartBoundary();
}
/**
* Return an {#link HttpEntity} for the given part Object.
* #param part the part to return an {#link HttpEntity} for
* #return the part Object itself it is an {#link HttpEntity},
* or a newly built {#link HttpEntity} wrapper for that part
*/
protected HttpEntity<?> getHttpEntity(Object part) {
return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<Object>(part));
}
/**
* Return the filename of the given multipart part. This value will be used for the
* {#code Content-Disposition} header.
* <p>The default implementation returns {#link Resource#getFilename()} if the part is a
* {#code Resource}, and {#code null} in other cases. Can be overridden in subclasses.
* #param part the part to determine the file name for
* #return the filename, or {#code null} if not known
*/
protected String getFilename(Object part) {
if (part instanceof Resource) {
Resource resource = (Resource) part;
String filename = resource.getFilename();
if (filename != null && this.multipartCharset != null) {
filename = MimeDelegate.encode(filename, this.multipartCharset.name());
}
return filename;
}
else {
return null;
}
}
private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
writeNewLine(os);
}
private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
os.write('-');
os.write('-');
os.write(boundary);
os.write('-');
os.write('-');
writeNewLine(os);
}
private static void writeNewLine(OutputStream os) throws IOException {
os.write('\r');
os.write('\n');
}
/**
* Implementation of {#link org.springframework.http.HttpOutputMessage} used
* to write a MIME multipart.
*/
private static class MultipartHttpOutputMessage implements HttpOutputMessage {
private final OutputStream outputStream;
private final HttpHeaders headers = new HttpHeaders();
private boolean headersWritten = false;
public MultipartHttpOutputMessage(OutputStream outputStream) {
this.outputStream = outputStream;
}
#Override
public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
#Override
public OutputStream getBody() throws IOException {
writeHeaders();
return this.outputStream;
}
private void writeHeaders() throws IOException {
if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
byte[] headerName = getAsciiBytes(entry.getKey());
for (String headerValueString : entry.getValue()) {
byte[] headerValue = getAsciiBytes(headerValueString);
this.outputStream.write(headerName);
this.outputStream.write(':');
this.outputStream.write(' ');
this.outputStream.write(headerValue);
writeNewLine(this.outputStream);
}
}
writeNewLine(this.outputStream);
this.headersWritten = true;
}
}
private byte[] getAsciiBytes(String name) {
try {
return name.getBytes("US-ASCII");
}
catch (UnsupportedEncodingException ex) {
// Should not happen - US-ASCII is always supported.
throw new IllegalStateException(ex);
}
}
}
/**
* Inner class to avoid a hard dependency on the JavaMail API.
*/
private static class MimeDelegate {
public static String encode(String value, String charset) {
try {
return MimeUtility.encodeText(value, charset, null);
}
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
}
}
The bean converter implementation
public class MyRequestBodyHttpMessageConverter extends
AbstractRequestBodyFormHttpMessageConverter<MyRequestBody> {
public MyRequestBodyHttpMessageConverter() {
super(MyRequestBody.class);
}
#Override
protected MyRequestBody buildObject(MultiValueMap<String, Object> valueMap) {
MyRequestBody parsed = new MyRequestBody();
parsed.setId(Long.valueOf((String)valueMap.get("id").get(0)));
parsed.setName((String)valueMap.get("name").get(0));
parsed.setRequestParams(valueMap);
return parsed;
}
}
And finally the MyRequestBody DTO (the MyRequestBody was the same just with different name)
#JacksonXmlRootElement
public class MyRequestBody implements RequestParamSupport, Serializable {
#JsonIgnore
private transient MultiValueMap<String, Object> requestParams;
#JacksonXmlProperty
private Long id;
#JacksonXmlProperty
private String name;
//empty constructor, getters, setters, tostring, etc
#Override
public MultiValueMap<String, Object> getRequestParams() {
return requestParams;
}
}
** Finally my answers: **
How I configure Spring to accept both types?
As you can see, you have to have your own form-data to your bean converter.
(Do not forget that you have to use #ModelAttribute when you are mapping from form data and not #RequestBody.)
Also should I split the Rest controller into different files?
No, that is not necessary, just register your converter.

How to redirect ftl in spring boot?

I have already created an e mail confirmation in spring boot it is working nicely, and also I created a link when a user click it should say "confirmed"but I did not figure it out how to do that?
E mail sender java class:
#Component
public class EmailSender {
#Autowired
JavaMailSender javaEmailSender;
public void sendEmail(String to, String subject, String text) throws MessagingException {
MimeMessage message = javaEmailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message,
MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED,
StandardCharsets.UTF_8.name());
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text, true);
helper.addInline("logo.jpg", new ClassPathResource("./images/logo.jpg"));
javaEmailSender.send(message);
}
}
E mail template Loader:
#Component
public class EmailTemplateLoader {
#Autowired
private Configuration freemakerConfiguration;
public String getEmailBody(String name, String confirmationLink) throws TemplateException {
try {
Template template = freemakerConfiguration.getTemplate("EmailConfirmation.ftl");
Map<String, Object> data = new HashMap<String, Object>();
data.put("name", name);
data.put("confirmationLink", confirmationLink);
return FreeMarkerTemplateUtils.processTemplateIntoString(template, data);
} catch (IOException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
}
return "";
}
}
My signup Resource :
#Autowired
private SignupService signupService;
#Autowired
private HttpServletRequest httpServletRequest;
#RequestMapping(value = "user/signup", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> signup(#RequestBody UserType user) throws SignUpException {
URL requestUrl = null;
try {
requestUrl = new URL(httpServletRequest.getRequestURL().toString());
} catch (MalformedURLException e) {
logger.debug("Malformed Request Url");
}
logger.debug(requestUrl.toString());
signupService.signUp(user, requestUrl.getHost());
return new ResponseEntity<>(HttpStatus.OK);
}
#RequestMapping(value = "user/confirmation", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> confirmSignUp(#RequestParam("u") String loginName, #RequestParam("p") String token) {
try {
signupService.emailConfirmation(loginName, token);
return new ResponseEntity<>(HttpStatus.OK);
} catch (SignUpException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}
So, As I told I can send an email successfully, but I could not sort how I can write confirmation successfully

How to set priority in ExceptionHandling via ControllerAdvice

I was implement 2 ControllersAdvice to. handle exception
CommonAdvice and UserAdvice
Common Advice
#ControllerAdvice(annotations = RestController.class)
public class CommonAdvice {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionBean> handleException(Exception e) {
ExceptionBean exception = new ExceptionBean(Causes.ANOTHER_CAUSE);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UserAdvice
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
public class UserAdvice {
#ExceptionHandler(NotUniqueUserLoginException.class)
public ResponseEntity<ExceptionBean> handleAlreadyFound(NotUniqueUserLoginException e) {
System.out.println("this is me : " + Causes.USER_ALREADY_EXIST.toString());
ExceptionBean exception = new ExceptionBean(Causes.USER_ALREADY_EXIST);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
And now, when I throw NotUniqueUserException, this is a CommonAdvice which handle and exception.
I tested and UserAdvice works fine.
There is the way to set priority on this classes ?
#Edit - add Controllel Mapping
#RequestMapping(value = "add", method = RequestMethod.POST)
public ResponseEntity<GT_User> addUser(#RequestBody GT_User newUser) throws NotUniqueUserLoginException, Exception {
if (this.userService.exist(newUser.getLogin())) {
throw new NotUniqueUserLoginException(Causes.USER_ALREADY_EXIST.toString());
} else {
GT_User addesUser = this.userService.addUser(newUser);
return new ResponseEntity<GT_User>(addesUser, HttpStatus.OK);
}
}
To set Higher priority to an ControllerAdvice on add :
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.HIGHEST_PRECEDENCE)
public class UserAdvice {
...
}
To set Lower priority to an ControolerAdvice on add
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.LOWEST_PRECEDENCE)
public class CommonAdvice {
...
}

#RestController cannot convert from ResponseEntity<Void>

I've got following code for my REST Controller:
#RequestMapping(value = "foo", method = RequestMethod.GET)
public ResponseEntity<Result> doSomething(#RequestParam int someParam)
{
try
{
final Result result = service.getByParam(someParam);
if (result == null)
{
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else
{
return ResponseEntity.ok(result);
}
} catch (Exception ex)
{
LOG.error("Error blah", ex);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I would like to use ResponseEntity.noContent().build() but Eclipse gives me:
Type mismatch: cannot convert from ResponseEntity to
ResponseEntity
Is there any way to overcome this?
Update:
It is possible to create helper like this:
public class ResponseUtils
{
public static <T> ResponseEntity<T> noContent()
{
return withStatus(HttpStatus.NO_CONTENT);
}
public static <T> ResponseEntity<T> internalServerError()
{
return withStatus(HttpStatus.INTERNAL_SERVER_ERROR);
}
public static <T> ResponseEntity<T> accepted()
{
return withStatus(HttpStatus.ACCEPTED);
}
private static <T> ResponseEntity<T> withStatus(HttpStatus status)
{
return new ResponseEntity<T>(status);
}
}
So I can use it like:
return ResponseUtils.noContent();
But maybe there is built-in functionality for this stuff?
Is this that you want to achieve?
#RequestMapping(value = "foo", method = RequestMethod.GET)
public ResponseEntity<Result> doSomething(#RequestParam int someParam) {
try {
final Result result = service.getByParam(someParam);
if (result == null) {
return ResponseUtils.noContent();
} else {
return new ResponseEntity<Result>(result, null, HttpStatus.ACCEPTED);
}
} catch (Exception ex) {
return ResponseUtils.internalServerError();
}
}
//you forgot to add static keyword in this Utils class
public static class ResponseUtils{
public static <T> ResponseEntity<T> noContent(){
return withStatus(HttpStatus.NO_CONTENT);
}
public static <T> ResponseEntity<T> internalServerError(){
return withStatus(HttpStatus.INTERNAL_SERVER_ERROR);
}
public static <T> ResponseEntity<T> accepted(){
return withStatus(HttpStatus.ACCEPTED);
}
private static <T> ResponseEntity<T> withStatus(HttpStatus status){
return new ResponseEntity<T>(status);
}
}
Check imports, I am using:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.xml.transform.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

Resources