How to validate a model model in web flux SpringBoot - spring-boot

I'm trying to move from traditional approach to Reactive style. Early days for me. One of the challenge I came into and could not make much progress is on model validation. With RestControllers, it was as easy as #Valid.
I don't see anything out there to make it happen for Webflux way of doing things
package com.reactive.sbhello.handler;
import com.reactive.sbhello.model.Order;
import com.reactive.sbhello.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import javax.validation.Validator;
#Component
public class OrderHandler {
#Autowired
private OrderService orderService;
private final Validator validator;
public OrderHandler(Validator validator) {
this.validator = validator;
}
public Mono<ServerResponse> getAll(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(orderService.getAllOrders(),
Order.class
);
}
public Mono<ServerResponse> getOrderInfo(ServerRequest request) {
var orderId = request.pathVariable("orderId");
var response = orderService.getOrderById(Integer.parseInt(orderId));
return response.collectList()
.flatMap(orders -> {
if(orders.isEmpty()) {
return ServerResponse.badRequest().body(BodyInserters.fromValue("Invalid OrderId"));
} else {
return ServerResponse.ok().body(BodyInserters.fromValue(orders));
}
});
}
public Mono<ServerResponse> addOrder(ServerRequest request) {
return request.bodyToMono(Order.class)
.flatMap(order -> orderService.addOrder(order))
.flatMap(order -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(order)));
}
}
"addOrder" function at the moment lacks any validation. As a result, null values go through. Is there anyway to validate apart from doing it in service and bubble up the error? Or should I stick to RestController approach and still use streaming from there.

Related

How to implement the GET request using ServiceLocatorFactoryBean ( Factory Method design Pattern)

I thank you ahead for your time to read my request. I'm new to the Spring Service Locator Factory Method design Pattern and I don't understand the approach behind it. However I followed a turtorial and have been able to implement the Post request for my user registratio spring maven application. My src/main/java folder cointains this five packages:
Config
Controller
Model
Registry
Service
The Config package is to centralize the creation of users and its java class is as bellow:
package com.nidservices.yekoregistration.config;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ServiceLocatorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.nidservices.yekoregistration.registry.ServiceRegistry;
#Configuration
public class UserConfig {
#Bean
public FactoryBean<?> factoryBean() {
final ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
bean.setServiceLocatorInterface(ServiceRegistry.class);
return bean;
}
}
The Registry package is to adapt the service base on the type of entity to create and is as bellow:
package com.nidservices.yekoregistration.registry;
public interface AdapterService<T> {
public void process(T request);
}
package com.nidservices.yekoregistration.registry;
public interface ServiceRegistry {
public <T> AdapterService<T> getService(String serviceName);
}
The Service package contains the different types of entity that inherit the User Model and the User Model is as bellow:
public class User implements Serializable {
private UUID id;
private String userIdentifier;
private String userType;
public String getUserIdentifier() {
return userIdentifier;
}
public void setUserIdentifier(String userIdentifier) {
this.userIdentifier = userIdentifier;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
#Override
public String toString() {
return "User [userIdentifier=" + userIdentifier + ", UserType=" + userType + "]";
}
}
And the Post Request defined in the Controller is as bellow:
package com.nidservices.yekoregistration.controller;
import org.springframework.beans.factory.annotation.Autowired;
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 com.nidservices.yekoregistration.model.User;
import com.nidservices.yekoregistration.registry.ServiceRegistry;
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
private ServiceRegistry serviceRegistry;
#PostMapping
public void processStudentDetails(#RequestBody User user) {
serviceRegistry.getService(user.getUserType()).process(user);
}
}
Now I'm struggling to make the GET Request to get all created users. I'm used with the DAO design pattern and very new with the concept behind ServiceLocatorFactoryBean. I appreciate your help to help me implement my CRUD endpoints using ServiceLocatorFactoryBean. Thanks in advance.

Spring rest controller does not validate my DTO

I have this request and response:
#Data
public class TestRequestDto {
#Min(7)
private String name;
}
#Data
public class TestResponseDto {
private String response;
}
And I have a controller:
package com.example.validation.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
#Slf4j
#RestController
public class TetController {
#PostMapping("/test")
public TestResponseDto getTestResponseDto(#Valid #RequestBody TestRequestDto request){
log.info(request.getName());
TestResponseDto response = new TestResponseDto();
response.setResponse("response");
return response;
}
}
I send a post request({"name":"test"}) with an invalid name but it works. What am I doing wrong?
Starting with Boot 2.3, we also need to explicitly add the spring-boot-starter-validation dependency

Spring - Failed to convert property value of type java.lang.String to required type

I am making a project of the Housing Association in Spring.
When I'm trying to add an object to my list of apartments I'm getting an error that is written somehow on the page:
https://s28.postimg.org/vrhy6mbd9/blad.jpg
Apartments have relation Many to One Building.
Apartment Controller:
package pl.dmcs.spoldzielnia.controllers;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import pl.dmcs.spoldzielnia.domain.Apartment;
import pl.dmcs.spoldzielnia.service.ApartmentService;
import pl.dmcs.spoldzielnia.service.BuildingService;
#Controller
#SessionAttributes
public class ApartmentController {
#Autowired
ApartmentService apartmentService;
#Autowired
BuildingService buildingService;
#RequestMapping("admin/apartment")
public String listApartment(Map<String, Object> map, HttpServletRequest request) {
int apartmentId = ServletRequestUtils.getIntParameter(request, "apartmentId" , -1);
if (apartmentId > 0)
{
Apartment apartment = apartmentService.getApartment(apartmentId);
apartment.setBuilding(buildingService.getBuilding(apartmentService.getApartment(apartmentId).getBuilding().getId()));
map.put("selectedBuilding", apartmentService.getApartment(apartmentId).getBuilding().getId());
map.put("apartment", apartment);
}
else
map.put("apartment", new Apartment());
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
#RequestMapping(value = "admin/addApartment", method = RequestMethod.POST)
public String addContact(#ModelAttribute("apartment") Apartment apartment, BindingResult result,
HttpServletRequest request, Map<String, Object> map) {
if (result.getErrorCount()==0)
{
if (apartment.getId()==0)
{
if (apartment.getBuilding().getId() > 0)
apartment.setBuilding(buildingService.getBuilding(apartment.getBuilding().getId()));
apartmentService.addApartment(apartment);
}
else
{
apartmentService.editApartment(apartment);
}
return "redirect:/admin/apartment.html";
}
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
#RequestMapping("admin/delete/apartment/{apartmentId}")
public String deleteApartment(#PathVariable("apartmentId") Integer apartmentId) {
apartmentService.removeApartment(apartmentId);
return "redirect:/admin/apartment.html";
}
// #RequestMapping("/apartment")
// public ModelAndView showContacts() {
//
// return new ModelAndView("apartment", "command", new Apartment());
// }
Domain:
package pl.dmcs.spoldzielnia.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name="apartment")
public class Apartment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
int id;
#Column(name="apartmentNumber", nullable=false)
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne
private Building building;
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
}
}
}
Building Service Implementation:
package pl.dmcs.spoldzielnia.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pl.dmcs.spoldzielnia.dao.BuildingDAO;
import pl.dmcs.spoldzielnia.domain.Building;
import pl.dmcs.spoldzielnia.domain.Building;
#Service
#Transactional
public class BuildingServiceImpl implements BuildingService{
#Autowired
BuildingDAO buildingDAO;
#Transactional
public void addBuilding(Building building) {
buildingDAO.addBuilding(building);
}
#Transactional
public List<Building> listBuilding() {
return buildingDAO.listBuilding();
}
#Transactional
public Building getBuilding(int id) {
return buildingDAO.getBuilding(id);
}
#Transactional
public void removeBuilding(int id) {
buildingDAO.removeBuilding(id);
}
#Transactional
public void editBuilding(Building building) {
buildingDAO.editBuilding(building);
}
}
Could you help me to solve my problem?
The problem is that you are assuming that Spring MVC is going to be able to fill your Apartment object from the data passed. From the form it looks like the Building number is 12, which probably is a unique identifier for the Building in the database, but how is Spring MVC going to know how to go to the database, retrieve the proper building object and put it into the Apartment object?
Remember that objects mapped through SpringMVC parameters are regular Java POJOs, not Hibernate attached entities. So, when the mapping occurs SpringMVC is trying to put "12" into the building attribute of type Building into your POJO (which explains the error you are getting).
You have two options:
First, you can register a custom formatter, that will use the passed id to retrieve a Building from the database:
import org.springframework.core.convert.converter.Converter;
public class BuildingIdToBuildingConverter implements Converter<String, Building> {
private BuildingService buildingService;
public BuildingIdToBuildingConverter(BuildingService buildingService) {
this.buildingService = buildingService;
}
#Override
public Building convert (String id) {
return buildingService.getBuilding(id);
}
}
And register it:
public class AppConfig extends WebMvcConfigurerAdapter {
...
#Bean
public BuildingService buildingService(){
return new BuildingService();
}
#Override
public void addFormatters (FormatterRegistry registry) {
registry.addConverter(new BuildingIdToBuildingConverter(buildingService()));
}
}
Second, do this work manually by sending the building id in a separate parameter:
#RequestMapping(value = "admin/addApartment", method = RequestMethod.POST)
public String addContact(#ModelAttribute("apartment") Apartment apartment, #RequestParam("buildingId") String buildingId, BindingResult result, HttpServletRequest request, Map<String, Object> map) {
if (result.getErrorCount()==0){
if (apartment.getId()==0){
apartment.setBuilding(buildingService.getBuilding(buildingId));
apartmentService.addApartment(apartment);
}
}
else{
apartmentService.editApartment(apartment);
}
return "redirect:/admin/apartment.html";
}
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
And change your HTML accordingly to send the buildingId value.

Spring-Boot - Error Handling

I'm trying to write error handler in Spring-Boot for my controllers that would catch most possible errors (Spring, sql etc.). So far I'm able to get JSON response with Nulls however i'm unable to put any data inside. When I try to get error message in I just receive a blank page.
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
#RestController
public class BasicErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#RequestMapping(value=ERROR_PATH)
#ExceptionHandler(value = {NoSuchRequestHandlingMethodException.class, SQLException.class, IOException.class, RuntimeException.class, Exception.class})
public ErrorBody defaultErrorHandler(HttpServletRequest request, Exception e) {
ErrorBody eBody = new ErrorBody();
eBody.setMessage(e.getCause().getMessage());
return eBody;
}
}
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class ErrorBody {
private String dateTime;
private String exception;
private String url;
private String message;
}
Yo can do something like this:
#ControllerAdvice
public class ControllerExceptionTranslator {
#ExceptionHandler(EntityNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
SimpleErrorMessage handleException(EntityNotFoundException exception){
log.debug("Entity Not Found Exception {}",exception.getMessage());
log.trace(exception.getMessage(),exception);
return new SimpleErrorMessage("Entity not found","This resource was not found");
}
#ExceptionHandler({UsernameNotFoundException.class})
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ResponseBody
SimpleErrorMessage handleException(UsernameNotFoundException exception){
log.debug("Username not found {}",exception.getLocalizedMessage());
log.trace(exception.getMessage(),exception);
return new SimpleErrorMessage("Unaouthorized"," ");
}
}
I was able to get to data about errors and send them as json properly by using "HttpServletRequest request" and reading information from request.
#RequestMapping(value = ERROR_PATH)
public ErrorBody defaultErrorHandler(HttpServletRequest request) {....}
Here this is an example of #ExceptionHandler(Exception.class)
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
You can use #ControllerAdvice
package demo.controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
#ControllerAdvice
public class ExceptionControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
System.out.println("controller advice: init binder");
}
#ExceptionHandler(Exception.class)
public String exception(Exception e) {
System.out.println("controller advice: exception Handler");
System.out.println(e.getMessage());
return "error";
}
#ModelAttribute
public void modelAttribute(){
System.out.println("controller advice:model Attribute");
}
}

Rest easy response status + body

I have following method in my rest service:
#POST
#Path("/create")
#ResponseStatus(HttpStatus.CREATED)
#Consumes(MediaType.WILDCARD)
public String create( .... ) {.... return json;}
so I want to get a response with json in body and status code CREATED.
The problem is: I can't get a response the CREATED status.
The status code is allways OK, so it seems that "#ResponseStatus(HttpStatus.CREATED)" is just ignored...
Can somebody help me with it?
I'm using hibernate 4.1, spring 3.1 and resteasy 2.3
As far as I know, it's not possible to achieve this by annotating the method with #org.springframework.web.bind.annotation.ResponseStatus.
You can return javax.ws.rs.core.Response from your method:
return Response
.status(Response.Status.CREATED)
.entity("ok")
.build();
Or you can have org.jboss.resteasy.spi.HttpResponse injected, and set the status code directly.
There might be more ways of doing this, but I'm only aware of these two.
Working testcase:
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.interception.PostProcessInterceptor;
import org.junit.Assert;
import org.junit.Test;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
public class ResponseTest {
#Path("/")
public static class Service {
#Context HttpResponse response;
#GET
#Path("/1")
public Response createdUsingResponse() throws NotFoundException {
return Response
.status(Response.Status.CREATED)
.entity("ok")
.build();
}
#GET
#Path("/2")
public String created() throws NotFoundException {
response.setStatus(Response.Status.CREATED.getStatusCode());
return "ok";
}
}
public static class Interceptor implements PostProcessInterceptor {
#Context HttpResponse response;
#Override
public void postProcess(ServerResponse response) {
if(this.response.getStatus() != 0){
response.setStatus(this.response.getStatus());
}
}
}
#Test
public void test() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(new Service());
dispatcher
.getProviderFactory()
.getServerPostProcessInterceptorRegistry()
.register(new Interceptor());
{
MockHttpRequest request = MockHttpRequest.get("/1");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assert.assertEquals(201, response.getStatus());
}
{
MockHttpRequest request = MockHttpRequest.get("/2");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assert.assertEquals(201, response.getStatus());
}
}
}

Resources