How to call another rest api from my controller in Micronaut like in Spring-Boot RestTemplate? - resttemplate

I have the following function from Spring Boot. I cannot do it with declarative client thus my uri domain changed after every call so i need a RestTemplate like in Spring Boot.
How can i achieve the same in Micronaut?
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}

Something like this is a good starting point...
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import javax.inject.Inject;
#Controller("/")
public class SomeController {
// The url does not have to be
// hardcoded here. Could be
// something like
// #Client("${some.config.setting}")
#Client("http://localhost:8080")
#Inject
RxHttpClient httpClient;
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}
I hope that helps.
EDIT
Another similar approach:
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
#Controller("/")
public class SomeController {
private final RxHttpClient httpClient;
public SomeController(#Client("http://localhost:8080") RxHttpClient httpClient) {
this.httpClient = httpClient;
}
#Get("/someuri")
public HttpResponse someMethod() {
String result = httpClient.toBlocking().retrieve("/springrestexample/employees.xml");
System.out.println(result);
// ...
return HttpResponse.ok();
}
}

Related

How to pass error details to model when create a custom error handler?

I created a custom error page to replace the default whitelabel based on this tutorial. It worked fine but I need to pass other attributes to the page so I changed my code to intercept the error endpoint based on the geoand's answer here.
Here is my final code:
#Controller
public class ErroHandlerController implements ErrorController {
#Value("${terena.midas.location}")
private String midasLocation;
#RequestMapping("/error")
public String handleError( Model model ) {
model.addAttribute( "midasLocation", midasLocation );
return "error";
}
#Override
public String getErrorPath() {
return "/error";
}
}
Well the code worked sending my variable midasLocation but I lost the error details like path, status,message, etc... How can I bring them back again?
You need to use the ErrorAttributes which "provides access to error attributes which can be logged or presented to the user".
Take a look:
at how the default Spring Error Controller does it: BasicErrorController.java
LogicBig -
Spring Boot - Using ErrorAttributes in our custom ErrorController
Basic functionality:
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.context.request.WebRequest;
#Controller
public class ErrorHandler implements ErrorController {
private final ErrorAttributes errorAttributes;
public ErrorHandler(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#GetMapping("/error")
public String handleError(Model model, WebRequest webRequest) {
model.addAttribute("midasLocation", "xxx");
final Throwable error = errorAttributes.getError(webRequest);
model.addAttribute("exception", error);
model.addAttribute("message", error == null ? "" : error.getMessage());
return "error";
}
#Override public String getErrorPath() {
return "/error";
}
#GetMapping("/throwErrorForTest")
public String throwError() {
throw new RuntimeException("my exception");
}
}

Spring boot post request model validation

What is recommended/best way to validate the post request DTO bean ?
If validation failed I need to send customized error message like
{
"code": "invalid_fields",
"fields": {
"email": "Required",
"password": "Required",
}
}
DTO model
public class SignUpRequest {
#JsonProperty("email")
String email;
#JsonProperty("password")
String password;
public Result validate(){
}
}
controller
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
SignUpRequest DTO has the method validate.
What is the spring way of doing the validation ?
Thanks.
You can use the following technique.
add the following dependencies in your gradle/maven file
compile "javax.validation:validation-api:2.0.1.Final"
compile "org.hibernate.validator:hibernate-validator:6.0.9.Final"
Hibernate-validator is implementation of validation-api 2.0
Add Validated annotation to your controller class
import org.springframework.validation.annotation.Validated;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
}
Add Valid annotation to your method parameter
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
Add Validated annotation to your dto class
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Email;
#Validated
public class SignUpRequest {
#JsonProperty("email")
#Email
String email;
#JsonProperty("password")
#NotNull
String password;
}
Add ExceptionTranslator with RestControllerAdvice annotation
#RestControllerAdvice
public class ExceptionTranslator {
/**
* Exception handler for validation errors caused by method parameters #RequesParam, #PathVariable, #RequestHeader annotated with javax.validation constraints.
*/
#ExceptionHandler
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException exception) {
List<ApiError> apiErrors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
String value = (violation.getInvalidValue() == null ? null : violation.getInvalidValue().toString());
apiErrors.add(new ApiError(violation.getPropertyPath().toString(), value, violation.getMessage()));
}
return ResponseEntity.badRequest().body(apiErrors);
}
}
Create ApiError class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApiError {
#JsonIgnore
private int code;
private String field;
private String value;
private String message;
public ApiError(String message) {
this.message = message;
}
public ApiError(String field, String value, String message) {
this.field = field;
this.value = value;
this.message = message;
}
}
Now if password field is missed you'll see the following response structure:
[
{
"field": "password",
"message": "must be filled"
}
]
If you would like to use some custom logic to validate your fields you may use the following approach
Create specific annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = ContactRequiredParametersValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface ContactRequiredParameters {
String message() default
"Email or phone must be filled";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator
import org.apache.commons.lang.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ContactRequiredParametersValidator implements ConstraintValidator<ContactRequiredParameters, Object[]> {
#Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null) {
return true;
}
if (!(value[0] instanceof SignUpRequest)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
SignUpRequest contact = (SignUpRequest) value[0];
return StringUtils.isNotEmpty(contact.getPassword());
}
}
add #ContactRequiredParameters annotation to your method in controller
#PostMapping(value = "/register")
#ContactRequiredParameters
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest)
That's all. Hope it helps
Spring boot supports validation out of the box using validation-api which is included with spring web mvc starter:
#RestController
#RequiredArgsConstructor
public class TestController {
#PutMapping(value = "/", consumes = APPLICATION_JSON_VALUE)
#ResponseStatus(NO_CONTENT)
public void test(#Valid #RequestBody final SignUpRequest params) {
...
}
}
You can annotate your SignUpRequest using annotations such as javax.validation.constraints.NotNull and other more complex ones.
the error messages can be customised with message properties or hard coded strings if i18n/l10n is of less interest to you.
Sample here: https://spring.io/guides/gs/validating-form-input/
If you want behaviour outside of the provided annotations you can write a custom annotation that can do that, e.g.
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = NotPastValidator.class)
#Documented
public #interface NotPast {
String message() default "date must not be in the past";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then:
public class NotPastValidator implements ConstraintValidator<NotPast, LocalDate> {
#Override
public void initialize(final NotPast constraintAnnotation) {
// nothing to do.
}
#Override
public boolean isValid(final LocalDate value, final ConstraintValidatorContext context) {
// As the Bean Validation specification recommends, we consider null values as being valid.
return value == null || isDateNotPast(value);
}
private boolean isDateNotPast(final LocalDate value) {
return ...
}
}
And finally just annotate your field:
#NotPast
Of course this is just an example with some code I previously used, you'll need to adapt to your needs.
If you don't want to use the validator API at all you can equally just write your own code to programatically check and throw some type of custom exception when invalid. This can then be caught in the controller and you can send what ever response you want, e.g.
#RestController
public class PaymentController {
#PostMapping(value ="/", consumes = APPLICATION_JSON_VALUE)
public void makePayment(#RequestBody final PaymentParams params) {
// validationService.validate(params);
}
#ExceptionHandler(MyValidationException.class)
public ResponseEntity<ExceptionDto> paymentCardException(final MyValidationException e) {
return status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(new ExceptionDto(e.getMessage));
}
}
I'd say given the validation API is well supported by spring, to me it makes sense to apply declarative validations where possible when using this stack. Custom rules can be a little painful, but you can use a multi faceted approach with some annotation based and equally you can perform some more complex validations in your own service.
This is a custom validation.
#PostMapping
private ResponseEntity<?> addMessage(#RequestBody Message message) {
Map<String, String> response = new HashMap<>();
if (message.getInputMessage() == null || message.getInputMessage().equals("")) {
response.put("status", "E");
response.put("message", "input message can not be empty");
return ResponseEntity.ok(response);
}
int id = messageService.addMessage(message);
if (id <= 0) {
response.put("status", "E");
response.put("message", "add message has error");
return ResponseEntity.ok(response);
}
response.put("status", "S");
response.put("message", "success");
return ResponseEntity.ok(response);
}

Spring Boot Hibernate RestFull Service PostgreSQL

I'm new user of Spring and I want develop a RestFull Service with Hibernate-PostGreSQL and Spring Boot. I try to learn with the documentation of Spring but I have lot of problem to deploy a simple service.
I don't use XML file properties but a Java Class.
Here are my different files :
PersistanceJPAConfig.java :
package com.spring.configuration;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
#Configuration
public class PersistenceJPAConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource driver = new DriverManagerDataSource();
driver.setDriverClassName("org.postgresql.Driver");
driver.setUrl("jdbc:postgresql://localhost:5432/test");
driver.setUsername("test");
driver.setPassword("test");
return driver;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setDataSource(dataSource());
return factory;
}
#Bean
#Autowired
public JpaTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
I have a classic model and here is the Repository :
package com.spring.persistence.repositories;
import com.spring.persistence.model.ApplicationUser;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
#Qualifier(value = "applicationUserRepository")
public interface ApplicationUserRepository extends JpaRepository<ApplicationUser,Long>{
}
A Simple service :
package com.spring.persistence.service;
import com.spring.persistence.model.ApplicationUser;
import com.spring.persistence.repositories.ApplicationUserRepository;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Transactional
public class ApplicationUserService {
private final ApplicationUserRepository applicationUserRepository;
#Autowired
public ApplicationUserService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
public ApplicationUser createUser(String username, String type, String country)
{
ApplicationUser user = new ApplicationUser(username,type,country);
return applicationUserRepository.saveAndFlush(user);
}
public List<ApplicationUser> getAllUser()
{
return applicationUserRepository.findAll();
}
public ApplicationUser getUser(Long id)
{
ApplicationUser user = null;
if(id != null)
{
user = applicationUserRepository.findOne(id);
}
return user;
}
public boolean deleteUser(Long id)
{
if(id != null)
{
try{
applicationUserRepository.delete(id);
return true;
}
catch(IllegalArgumentException ex)
{
ex.printStackTrace();
return false;
}
}
else
{
System.out.println("Id is null");
return false;
}
}
}
And finally the WebController :
#RestController
#RequestMapping(value = "/applicationuser")
public class ApplicationUserController {
#Autowired
private ApplicationUserService applicationUserService;
#RequestMapping(value="/",method = RequestMethod.GET)
#ResponseBody
public ApplicationUser index()
{
return applicationUserService.createUser("test", "test", "test");
}
}
It's possible is missing lot of things (Annotation,Initializer,Code) but I'm here to learn and any advices can help me.
Thanks for your answers
Spring Data REST
This project will allow you to achieve your goals with significantly less boilerplate code.
Follow the Accessing JPA Data with REST guide which demonstrates how to configure Spring Boot + Spring Data REST with absolutely minimal configuration.
Once you have a basic understanding, then you can add more functionality to meet your business requirements.
Detailed information is provided in the Spring Data REST Documentation

What's required to make mockMVC test a filter's init routine?

I have implemented the following CORS filter, which works when the code is executed on the server:
/*
* Copyright 2013 BrandsEye (http://www.brandseye.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.energyos.espi.datacustodian.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
/**
* Adds CORS headers to requests to enable cross-domain access.
*/
#Component
public class CORSFilter implements Filter {
private final Log logger = LogFactory.getLog(getClass());
private final Map<String, String> optionsHeaders = new LinkedHashMap<String, String>();
private Pattern allowOriginRegex;
private String allowOrigin;
private String exposeHeaders;
public void init(FilterConfig cfg) throws ServletException {
String regex = cfg.getInitParameter("allow.origin.regex");
if (regex != null) {
allowOriginRegex = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
} else {
optionsHeaders.put("Access-Control-Allow-Origin", "*");
}
optionsHeaders.put("Access-Control-Allow-Headers", "Origin, Authorization, Accept, Content-Type");
optionsHeaders.put("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
optionsHeaders.put("Access-Control-Max-Age", "1800");
for (Enumeration<String> i = cfg.getInitParameterNames(); i.hasMoreElements(); ) {
String name = i.nextElement();
if (name.startsWith("header:")) {
optionsHeaders.put(name.substring(7), cfg.getInitParameter(name));
}
}
//maintained for backward compatibility on how to set allowOrigin if not
//using a regex
allowOrigin = optionsHeaders.get("Access-Control-Allow-Origin");
//since all methods now go through checkOrigin() to apply the Access-Control-Allow-Origin
//header, and that header should have a single value of the requesting Origin since
//Access-Control-Allow-Credentials is always true, we remove it from the options headers
optionsHeaders.remove("Access-Control-Allow-Origin");
exposeHeaders = cfg.getInitParameter("expose.headers");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("CORSFilter processing: Checking for Cross Origin pre-flight OPTIONS message");
}
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
if ("OPTIONS".equals(req.getMethod())) {
allowOrigin = "*"; //%%%%% Test force of allowOrigin
if (checkOrigin(req, resp)) {
for (Map.Entry<String, String> e : optionsHeaders.entrySet()) {
resp.addHeader(e.getKey(), e.getValue());
}
// We need to return here since we don't want the chain to further process
// a preflight request since this can lead to unexpected processing of the preflighted
// request or a 40x - Response Code
return;
}
} else if (checkOrigin(req, resp)) {
if (exposeHeaders != null) {
resp.addHeader("Access-Control-Expose-Headers", exposeHeaders);
}
}
}
filterChain.doFilter(request, response);
}
private boolean checkOrigin(HttpServletRequest req, HttpServletResponse resp) {
String origin = req.getHeader("Origin");
if (origin == null) {
//no origin; per W3C specification, terminate further processing for both pre-flight and actual requests
return false;
}
boolean matches = false;
//check if using regex to match origin
if (allowOriginRegex != null) {
matches = allowOriginRegex.matcher(origin).matches();
} else if (allowOrigin != null) {
matches = allowOrigin.equals("*") || allowOrigin.equals(origin);
}
if (matches) {
// Activate next two lines and comment out third line if Credential Support is required
// resp.addHeader("Access-Control-Allow-Origin", origin);
// resp.addHeader("Access-Control-Allow-Credentials", "true");
resp.addHeader("Access-Control-Allow-Origin", "*");
return true;
} else {
return false;
}
}
public void destroy() {
}
}
The following JUnit test uses mockMVC but fails, because the CORSFilter's "init" logic is not being executed (proven by breakpointing the JUnit test):
package org.energyos.espi.datacustodian.integration.web.filters;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.FilterConfig;
import org.energyos.espi.datacustodian.web.filter.CORSFilter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("/spring/test-context.xml")
#Profile("test")
public class CORSFilterTests {
private final Log logger = LogFactory.getLog(getClass());
#Autowired
private CORSFilter filter;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac)
.addFilters(filter).build();
}
#Test
public void optionsResponse_hasCorrectFilters() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.options("/DataCustodian/oauth/token")
.header("Origin", "foobar")
.header("Access-Control-Allow-Origin", "*");
MvcResult result = mockMvc.perform(requestBuilder)
.andExpect(header().string("Access-Control-Allow-Origin", is("*")))
.andExpect(header().string("Access-Control-Allow-Methods", is("GET, POST, PUT, DELETE, OPTIONS")))
.andExpect(header().string("Access-Control-Allow-Headers", is("origin, authorization, accept, content-type")))
.andExpect(header().string("Access-Control-Max-Age", is("1800")))
.andReturn();
}
}
}
I have reviewed the available material on the internet, which seems to imply the ".addfilter(filter). element of the mockMVC #Before section should be executing the CORSFilter init routine. However, that is clearly NOT happening.
Any suggestions or recommendations would be greatly appreciated, as I am really stuck understanding how to get the "init" routine tested using the mockMVC capability.
The Spring MVC Test suite is not meant to test the container configuration, it is meant to test your MVC (#Controller and other mappings) configuration . Filter#init(ServletConfig) is a container managed method.
If you really need to test it, you can mock that too
#Before
public void setup() {
filter.init(someMockFilterConfig); // using a mock that you construct with init params and all
this.mockMvc = webAppContextSetup(this.wac)
.addFilters(filter).build();
}
After lots of tests, here's what we adopted:
for testing a #RestController use MockMvc.
for testing a Filter or other infrastructure elements, use TestRestTemplate.
With MockMvc, addFilter(Filter) did not result in the execution of the filter at all. The solution with TestRestTemplate is more primitive, but all Filters configured in your application/libraries are executed. Example:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MySpringBootApplication.class, webEnvironment=
SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyRestControllerTest {
#LocalServerPort
private int port;
#Test
public void myTestCase() throws Exception {
HttpStatus expectedStatusCode = HttpStatus.OK;
String expectedResponseBody = "{\"someProperty\" : \"someValue\" }";
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer YourTokenJwtForExample");
HttpEntity<String> entity = new HttpEntity<>(null, headers);
TestRestTemplate restTemplate = new TestRestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
"http://localhost:" + port + "/my-rest-uri",
HttpMethod.GET, entity, String.class);
Assert.assertEquals(expectedStatusCode, response.getStatusCode());
Assert.assertEquals(expectedResponseBody, response.getBody());
}
}
For a Spring Boot app, if #SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) is used then filter.init() is called automatically. if #SpringBootTest is used with default parameters then filter.init() needs to be invoked manually.
If you want a true unit test instead of an integration test, you may also want to take a look at org.springframework.mock.web.MockServletConfig available from
org.springframework:spring-test maven artifact
You can set up config parameters on the mock object. There are also mocks for HttpServletRequest, HttpServletResponse and FilterChain

Http Post with request content type form not working in Spring MVC 3

code snippet:
#RequestMapping(method = RequestMethod.POST)//, headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView create(#RequestBody UserAccountBean account) {
try{
accounts.put(account.assignId(), account);
}catch(RuntimeException ex)
{
return new ModelAndView("account/registerError");
}
return new ModelAndView("account/userVerification");
}
After receiving request, What I got is Http Status code 415:
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
If I change the code to this:
code snippet:
#RequestMapping(method = RequestMethod.POST,headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView create(#RequestBody UserAccountBean account) {
try{
accounts.put(account.assignId(), account);
}catch(RuntimeException ex)
{
return new ModelAndView("account/registerError");
}
return new ModelAndView("account/userVerification");
}
I will get 405 Method not allowed. Funny thing is in the allow header of response, it lists GET and POST as allowed methods.
I do have a class that does JOSN mapping:
#Component
public class JacksonConversionServiceConfigurer implements BeanPostProcessor {
private final ConversionService conversionService;
#Autowired
public JacksonConversionServiceConfigurer(ConversionService conversionService) {
this.conversionService = conversionService;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AnnotationMethodHandlerAdapter) {
AnnotationMethodHandlerAdapter adapter = (AnnotationMethodHandlerAdapter) bean;
HttpMessageConverter<?>[] converters = adapter.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJacksonHttpMessageConverter) {
MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ConversionServiceAwareObjectMapper(this.conversionService));
}
}
}
return bean;
}
}
Copied from Spring examples. works great with JSON content-type.
A more general question is how to make spring mvc request handlers work with different request content-types.
Any advice would be greatly appreciated.
Unfortunately FormHttpMessageConverter (which is used for #RequestBody-annotated parameters when content type is application/x-www-form-urlencoded) cannot bind target classes (as #ModelAttribute can).
Therefore you need #ModelAttribute instead of #RequestBody. If you don't need to pass different content types to that method you can simply replace the annotation:
#RequestMapping(method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute UserAccountBean account) { ... }
Otherwise I guess you can create a separate method form processing form data with the appropriate headers attribute:
#RequestMapping(method = RequestMethod.POST,
headers = "content-type=application/x-www-form-urlencoded")
public ModelAndView createFromForm(#ModelAttribute UserAccountBean account) { ... }
EDIT: Another possible option is to implement your own HttpMessageConverter by combining FormHttpMessageConverter (to convert input message to the map of parameters) and WebDataBinder (to convert map of parameters to the target object).
I was having HTTP response code of 415
My problems were resolved when I added Content Type to request header
e.g
"Content-Type: application/json"
At the heart of the problem, we wish to accept both application/json and application/x-www-form-urlencoded Content-types with the same request handler.
To do this, I use the #RequestBody, which was already working for application/json for me (and generally others from the threads I've found, but there is extra work so application/x-www-form-urlencoded can be used with #RequestBody.
First, create a new HttpMessageConverter capable of changing the request input to an object. I do this by reusing the FormHttpMessageConverter, which is already capable of changing the input to a MultiValueMap. I then change the MultiValueMap to a regular Map, and use Jackson to turn the Map to the desired object.
Here is the code for the HttpMessageConverter:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* <p>Converts HTTP requests with bodies that are application/x-www-form-urlencoded or multipart/form-data to an Object
* annotated with {#link org.springframework.web.bind.annotation.RequestBody} in the the handler method.
*
* #author Jesse Swidler
*/
public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> {
private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
private final ObjectMapper objectMapper = new ObjectMapper();
private static final LinkedMultiValueMap<String, String> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>();
private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS
= (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass();
#Override
public boolean canRead(Class clazz, MediaType mediaType) {
return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);
}
#Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return false;
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return formHttpMessageConverter.getSupportedMediaTypes();
}
#Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
Map<String, String> input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap();
return objectMapper.convertValue(input, clazz);
}
#Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException {
throw new UnsupportedOperationException("");
}
}
There are many different ways a Spring app might pick up that message converter. For me, it was accomplished in an XML file:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.terminal.core.services.config.ObjectHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
Using #ModelAttribute is indeed the preferred way to deal with form parameters.
Using JSON worked for me as well, I suppose it makes the JSON interpreter get the data from the body.
I was trying to use PUT though, which is a bit harder.
You can read my post about it here.
Below worked for me
On server side:
#RequestMapping(value = "test", method = RequestMethod.POST, consumes = {"application/xml", "application/json"})
#ResponseStatus(HttpStatus.OK)
public #ResponseBody
String methodName(#RequestBody EntityClassName entity) {
On client side:
String json = new JSONStringer().object()
.key("key").value("value")
.endObject()
.toString();
StringEntity se = new StringEntity(json);
se.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
request.setEntity(se);
HttpResponse response = client.execute(request);
I use this code for convert html form to json .
function ConvertFormToJSON(form) {
var array = $(form).serializeArray();
var json = {};
$.each(array, function() {
json[this.name] = this.value || '';
});
return json;
}
and use single quotations was wrong . I changed ' ' to " " and problem solved.

Resources