Converter works for RequestParameter but not for RequestBody field - spring-boot

I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}

Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}

I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}

Related

Spring validating a object before returning it in a function not working

I have a service, that gets an xml/json string, tries to read it as an pojo, then returns it. Then, I want to show the result in thymeleaf. I did that successfully, but - in the model I have validation annotations, but if I submit invalid information it accepts the value, although I validated the method. Here is my code:
Controller:
#Controller
public class ConvertController implements WebMvcConfigurer {
#Autowired
PrintJSON printJSON;
#Autowired
PrintXML printXML;
#Autowired
ReadJSON readJSON;
#Autowired
ReadXML readXML;
#GetMapping("/read")
public String showReadForm() {
return "read";
}
#PostMapping("/read")
public String read(#RequestParam(value = "convertFrom") String
convertFrom, String text, Model model){
if("json".equals(convertFrom)){
Book newBook = readJSON.read(text);
model.addAttribute("result", newBook);
return "converted";
}else if("xml".equals(convertFrom)){
Book newBook = readXML.read(text);
model.addAttribute("result", newBook);
return "converted";
}
return "read";
}
#GetMapping("/print")
public String showPrintForm(Book book){
return "convert";
}
#PostMapping("/print")
public String convert(#RequestParam(value = "convertTo") String
convertTo, #Valid Book book, Errors errors, Model model) {
if(errors.hasErrors()){
return "convert";
}
if("json".equals(convertTo)){
model.addAttribute("result", printJSON.getJSON(book));
return "converted";
}
if("xml".equals(convertTo)){
model.addAttribute("result", printXML.getXML(book));
return "converted";
}
return "convert";
}}
Service
public class ReadXML {
#Autowired
#Qualifier("XmlMapper")
XmlMapper xmlMapper;
#Valid
public Book read(String xml){
try{
#Valid Book book = xmlMapper.readValue(xml, Book.class);
return book;
}
catch(JsonProcessingException e){
e.printStackTrace();
return new Book();
}
}
}
Model
public class Book {
#NotEmpty
private String title;
private String description;
private Date publishDate;
private int ISBN;
private List<#Valid Author> authors;
#Override
public String toString(){
String bookString = String.format("Title: %s\nDescription: %s\nPublish Date: %s\nISBN: %s\nAuthor", title, description, publishDate, ISBN);
for(Author a : authors){
bookString += a.toString();
}
return bookString;
}
public String getTitle() {
return title;
}
public void setTitle(String title){
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description){
this.description = description;
}
public Date getPublishDate() {
return publishDate;
}
public void setPublishDate(String newPublishDate) throws ParseException {
Date publishDate = new SimpleDateFormat(Constants.dateFormat).parse(newPublishDate);
this.publishDate = publishDate;
}
public int getISBN() {
return ISBN;
}
public void setISBN(int ISBN){
this.ISBN = ISBN;
}
public void addAuthor(Author author) {
authors.add(author);
}
public List<Author> getAuthors(){
return authors;
}
}
Where is my problem???
Thank you!

Why does MongoRepository save return an empty json and save empty value when a variable is not empty?

I have a simple document:
#Document
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
public class ProductUnit {
#Id
String id;
private String name;
private Integer price;
private LocalDateTime localDateTime;
}
Simple MongoRepository :
public interface productRepo extends MongoRepository<ProductUnit,String> {
ProductUnit deleteByName(String name);
List<ProductUnit> findByPrice(Integer price);
}
and Service :
#Service
public class productServiseImpl implements productServise {
#Autowired
productRepo repository;
#Override
public ProductUnit saveOrUpdate(ProductUnit productUnit) {
System.out.println("inside save or update");
return repository.save(productUnit);
}
#Override
public List<ProductUnit> findAll() {
return repository.findAll();
}
#Override
public ProductUnit deleteUnitByPrice(String name) {
return repository.deleteByName(name);
}
#Override
public List<ProductUnit> findByPrice(Integer price) {
return repository.findByPrice(price);
}
}
Now , inside RestController , I pass id through a post request and use a random class to generate a random value of the price and name .At this stage everything is fine, i.e. all values were initialized correctly, but when it comes to service.saveOrUpdate(forSave) It stores the value incorrectly, i.e. the request returns an empty json and the findAll method returns a list of empty json.Can you tell me what the error is? thanks
#RestController
public class productUnitRestController {
#Autowired
productServise service;
#Autowired
Supplier<MetaInfGenerator> generatorSupplier;
#GetMapping(path = "/all")
public List<ProductUnit> getAllProoduct(){
return service.findAll();
}
#PostMapping(path = "/products")
public ProductUnit createProoduct(#RequestParam("id") Optional<String> newId){
System.out.println("***** iside PostMapping ******");
MetaInfGenerator generator = generatorSupplier.get();
System.out.println("***** supplier PostMapping ******");
ProductUnit forSave = ProductUnit.builder()
.id(newId.get())
.name(generator.getRandomString())
.price(generator.getRandomInteger())
.localDateTime(LocalDateTime.now()).build();
System.out.println(forSave);
return service.saveOrUpdate(forSave);
}
}

Can I return DTO and domain entities from services?

I have a spring-boot application and I use DTO like that:
Service
#Service
public class UnitOfMeasureServiceImpl implements IUnitOfMeasureService {
private final IUnitsOfMeasureRepository unitOfMeasureRepository;
#Autowired
public UnitOfMeasureServiceImpl(IUnitsOfMeasureRepository unitOfMeasureRepository) {
this.unitOfMeasureRepository = unitOfMeasureRepository;
}
#Override
public UnitOfMeasureDTO getUnitOfMeasureById(UUID id) {
Optional<UnitOfMeasure> optionalUnitOfMeasure = unitOfMeasureRepository.findById(id);
if (!optionalUnitOfMeasure.isPresent()){
// throw new ComponentNotFoundException(id);
return null;
}
return UnitOfMeasureDTO.factory(optionalUnitOfMeasure.get());
}
dto:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class UnitOfMeasureDTO {
private String id;
private String name;
private String description;
private String sourceInfoCompanyName;
private String originalId;
public static UnitOfMeasureDTO factory(UnitOfMeasure unitOfMeasure) {
UnitOfMeasureDTO dto = new UnitOfMeasureDTO();
dto.id = unitOfMeasure.getId().toString();
dto.name = unitOfMeasure.getName();
dto.description = unitOfMeasure.getDescription();
dto.sourceInfoCompanyName = unitOfMeasure.getSourceInfo().getSourceCompany().getName();
dto.originalId = unitOfMeasure.getOriginalId();
return dto;
}
}
controller:
#RestController
#RequestMapping(UnitOfMeasureController.BASE_URL)
public class UnitOfMeasureController {
public static final String BASE_URL = "/api/sust/v1/unitOfMeasures";
private final IUnitOfMeasureService unitOfMeasureService;
public UnitOfMeasureController(IUnitOfMeasureService unitOfMeasureService) {
this.unitOfMeasureService = unitOfMeasureService;
}
#GetMapping(path = "/{id}")
#ResponseStatus(HttpStatus.OK)
public UnitOfMeasureDTO getUnitOfMeasureDTO(#PathVariable("id") UUID id) {
UnitOfMeasureDTO unitOfMeasureDTO = unitOfMeasureService.getUnitOfMeasureById(id);
return unitOfMeasureDTO;
}
So in my service I have getUnitOfMeasureById(UUID id) that return a UnitOfMeasureDTO.
Now I need to call, from another service, getUnitOfMeasureById(UUID id) that return the domain entity UnitOfMeasure. I think it's correct to call a service method from another service (not a controller method!) and the separation between business logic is at the service layer. So is it correct to have 2 methods: getUnitOfMeasureDTOById and getUnitOfMeasureById in the service? (getUnitOfMeasureDTOById call getUnitOfMeasureById to avoid code duplication)

Java Spring 4 (Annotated) Rest Controller not being hit by REST Client tool in Firefox

Hi,
I have a problem that is very confusing for me because the mapping should work and it looks like it does map when the Spring Boot is started in debug mode. I don't know where else I can check for an obvious solution to this problem.
Here is the application.properties:
server.port=8082
server.contextPath = /
Here is the SpringBootInitializer class that adds a further "/api" to the >Servlet registration:
public class App extends SpringBootServletInitializer {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
final ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/api/*");
final Map<String, String> params = new HashMap<String, String>();
params.put("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
params.put("contextConfigLocation", "org.spring.sec2.spring");
params.put("dispatchOptionsRequest", "true");
registration.setInitParameters(params);
registration.setLoadOnStartup(1);
return registration;
}
//
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.initializers(new MyApplicationContextInitializer()).sources(App.class);
}
public static void main(final String... args) {
new SpringApplicationBuilder(App.class).initializers(new MyApplicationContextInitializer()).run(args);
}
}
Here is the Controler which adds a further "users" to the mapping. The method >which I have set a debug point is the findAll and requires no futher mapping to >get to it (i.e. the root of /users/:
#Controller
#RequestMapping(value = users)
public class UserController extends AbstractController<User> {
#Autowired
private IUserService userService;
public UserController() {
super(User.class);
}
// API
// find
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public void getItsWorking() {
System.out.println("It's Working!!!");
}
}
Here is the User entity:
#Entity
public class User implements IEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="user_id")
private Long user_id;
#Column(name = "username", unique = true, nullable = false)
private String name;
#Column(unique = true, nullable = false)
private String email;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private Boolean locked;
public User() {
super();
}
public User(final String nameToSet, final String passwordToSet, /*final
Set<Role> rolesToSet,*/ final Boolean lockedToSet) {
super();
name = nameToSet;
password = passwordToSet;
locked = lockedToSet;
}
// API
public Long getId() {
return user_id;
}
public void setId(final Long idToSet) {
user_id = idToSet;
}
public String getName() {
return name;
}
public void setName(final String nameToSet) {
name = nameToSet;
}
public String getEmail() {
return email;
}
public void setEmail(final String emailToSet) {
email = emailToSet;
}
public String getPassword() {
return password;
}
public void setPassword(final String passwordToSet) {
password = passwordToSet;
}
public Boolean getLocked() {
return locked;
}
public void setLocked(final Boolean lockedToSet) {
locked = lockedToSet;
}
}
Here is the output on my Spring Boot debug when it starts up:
Mapped "{[/users],methods=[GET]}" onto public
java.util.List<org.um.persistence.model.User>
org.um.web.controller.UserController.findAll(javax.servlet.http.HttpServletRequest)
So, it looks like it is mapping correctly, but when I hit it using the Rest >Client tool add on in Firefox, I get the following when doing a "GET" on the >following url: http://localhost:8082/api/users using Content-Type: application/json in my header .
What is going on? Very confused.
You should put a #RequestMapping("/api") on you class, and a #RequestMapping("/users") on your method (that should preferably return something to the client).
This ways your endpoint will be exposed as /api/users and you will be able to easily add further endpoints under /api/* into this class.

Spring MVC Data Binding: bind parameter to an object field with different name

Here is my #Controller:
#RequestMapping(value = "/add")
public void addMember(Member member) {
// ... ...
}
here is Member class:
class Member {
private Integer id;
private String username;
// ... ...
}
And here is the request url:
/add?memberId=1&username=bruce
How can I bind the memberId parameter to Member.id field?
Thanks!
If you don't want to use
#RequestMapping(value = "/add")
public void addMember(#RequestParam(value = "username") String username, ...) {
Member member = new Member(username, ...)
// ...
}
you can implement HandlerMethodArgumentResolver:
public class MemberHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return Member.class.equals(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String username = webRequest.getParameter("username");
...
return new Member(username, ...);
}
}
And in config:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean id="memberResolver" class="com.company ... MemberHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
or JavaConfig:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
...
#Override
public void addArgumentResolvers(List<Handlermethodargumentresolver> argumentResolvers) {
argumentResolvers.add(new MemberHandlerMethodArgumentResolver());
}
}
IMHO the simplest way to do that is to use an additional setter in Member class :
class Member {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setMemberId(Integer memberId) { // this one should to the trick
this.id = memberId;
}
// ... ...
}
You can use #JsonProperty Annotation for it.Like below.
class Member {
#JsonProperty("memberId")
private Integer id;
private String username;
// ... ...
}

Resources