I can't make spring serialize the response when results is array/list .
So when I call clients from RestController it does return [{},{},{}], instead of real objects, all other methods works just fine.
package com.test.Domain.Client;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.UUID;
#Entity
#Table(name = "client")
public class Client {
#Column(name = "client_id")
#Id
private UUID clientId;
#Column(name = "name")
private String name;
private Client() {
}
private Client(UUID clientId, String name) {
this.clientId = clientId;
this.name = name;
}
public static Client create(String name)
{
return new Client(UUID.randomUUID(), name);
}
}
package com.test.Rest;
import com.test.Domain.Calendar.AppointmentRepository;
import com.test.Domain.Client.Client;
import com.test.Domain.Client.ClientRepository;
import com.test.Domain.Worker.WorkerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
#org.springframework.web.bind.annotation.RestController
public class RestController {
#Autowired
private ClientRepository clientRepository;
#Autowired
private WorkerRepository workerRepository;
#Autowired
private AppointmentRepository appointmentRepository;
#RequestMapping(path = "/client", method = RequestMethod.POST)
public void registerClient(#RequestParam(name = "name") String name) {
this.clientRepository.save(Client.create(name));
}
#RequestMapping(path = "/clientCount", method = RequestMethod.GET)
public Long countClient() {
return this.clientRepository.count();
}
#RequestMapping(path = "/clients", method = RequestMethod.GET)
#ResponseBody
public List<Client> clients() {
List<Client> list = new ArrayList<Client>();
for (Client client : this.clientRepository.findAll()) {
list.add(client);
}
return list;
}
}
Jackson needs Getter and Setter methods in order to serialize the Client object properly into JSON. Therefore a list of empty objects is returned and the values for the members are missing. Add them to Client and the response should look fine.
Spring applies first registered applicable by response mime-type HttpMessageConverter implementation when serializing the response to /clients call. In your case this is some JSON serializer. As you have no JSON configuration specified on Client class the default POJO serializing approach is used: reflection scanning of object properties. As mentioned earlier your Client class doesn't define any properties (at least getters), so serializer do not detect any.
Please refer to the following article for a more detailed explanation: https://www.javacodegeeks.com/2013/07/spring-mvc-requestbody-and-responsebody-demystified.html
P.S. Marking method with #ResponseBody in #RestController annotated class is not necessary as itself is a convenience annotation aggregating #Controller and #ResponseBody.
Related
Basically, I have implemented this converter, to allow me to send JSON data as a "data" field alongside a file upload.
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import ****.DatasetUploadDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import lombok.SneakyThrows;
#Component
public class StringToDatasetUploadDtoConverter implements Converter<String,
DatasetUploadDTO> {
#Autowired
private ObjectMapper objectMapper;
#Override
#SneakyThrows
#JsonIgnoreProperties(ignoreUnknown=true)
#Valid
public DatasetUploadDTO convert(String source) {
return objectMapper.readValue(source, DatasetUploadDTO.class);
}
this is my dto class:
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import ****.models.ExtraMetaData;
#Data
#RequiredArgsConstructor
public class DatasetUploadDTO {
#Length(max = 0, message = "Id modification not permitted in this context.")
private String id;
#NotBlank(message = "Description is mandatory") #Length(min=3)
private String description;
private String authorId;
#NotNull
#NotBlank(message = "ExperimentId is mandatory") #Length(min=3)
private String experimentId;
private String type;
#NotNull
private Boolean isPublic;
#NotNull
private Boolean isMetaData;
#NotNull
private List<String> userPermission;
}
In my controller, I can successfully use this converter, and save to DB etc. with no problems. However, if I then add the #Valid annotation to attempt to validate according to this schema, it doesn't actually do anything:
#RequestMapping(path = "/dataset", method = RequestMethod.POST, consumes = {"multipart/form-data"})
ResponseEntity<?> postExperiment(#AuthenticationPrincipal UserDetailsImpl jwt , #RequestParam("data") /* Here nothing happens -> */ #Valid DatasetUploadDTO uploadDTO, #RequestParam("file") MultipartFile file) {
....
}
What can I do to achieve validation of this JSON field? If all else fails, is there some way I can implement validation within the body of the function?
I am trying to validate the json-resquest using hibernate-validator, it is working as expected but response is not there in postman.
Customer.java
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "cin", "firstName"})
public class Customer {
#JsonProperty("cin")
private String cin;
#JsonProperty("firstName")
#NotEmpty(message = "First Name must have some values")
#Size(min = 2, message = "First Name must greater or equal to 2 characters")
private String firstName;
//getters and setters
}
and Errors class - to wrap error in one object.
public class Errors {
private Integer status;
private String message;
private List<String> details;
public Errors(Integer status, String message, List<String> details) {
super();
this.status = status;
this.message = message;
this.details = details;
}
// Getters and Setters
}
ControllerAdvice class
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.ecommerce.ms.customer.model.Errors;
#ControllerAdvice
#ResponseBody
public class CustomerExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value=ConstraintViolationException.class)
public final ResponseEntity<Errors> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> details = ex.getConstraintViolations().parallelStream().map(e -> e.getMessage())
.collect(Collectors.toList());
Errors error = new Errors(HttpStatus.BAD_REQUEST.value(), "Request Validation Error", details);
return ResponseEntity.badRequest().body(error);
}
}
CustomerController.java
*
*/
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.ecommerce.ms.customer.api.service.CustomerService;
import com.ecommerce.ms.customer.model.Customer;
#RestController
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService customerService;
#GetMapping("/status")
public String getStatus() {
return "ok";
}
#PostMapping(consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public ResponseEntity<Customer> addCustomer(#Valid #RequestBody Customer customer) {
return ResponseEntity.accepted().body(customerService.addCustomer(customer));
}
}
Hibernate-validator is already added pom.xml and I am expecting the below reason.
{
"status":400,
"message": "Request Validation Error",
"details":["First Name must greater or equal to 2 characters"]
}
I am trying to to get proper response body but I couldn't find it in postman.
Looking at the ResponseEntityExceptionHandler there is no such method that handles ConstraintValidationExceptions, and therefore the custom method you have created is not being called.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html
As well:
You cannot catch ConstraintViolationException.class because it's not
propagated to that layer of your code, it's caught by the lower
layers, wrapped and rethrown under another type. So that the exception
that hits your web layer is not a ConstraintViolationException.
Ref: SpringBoot doesn't handle org.hibernate.exception.ConstraintViolationException
An example of proper usage is to use the method handleMethodArgumentNotValid and return the Errors as body:
#RestControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> responseBody = new LinkedHashMap<>();
List<String> allErrors = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach(error -> allErrors.add(error.getDefaultMessage()));
responseBody.put("Errors:", allErrors);
return new ResponseEntity<>(responseBody, headers, status);
}
}
Note: This is a project which has a connection with database on other tables. I just made a new table, but i must have something wrong in my codes, because i cant get what i want.
I have a City table, and this table has 3 columns, named id, name, city_id. And i imported a csv file, so when i query, I can see some data.
I wrote Entity, Repository, Controller, and Service, in Java on Eclipse
What should I do? For example, when i search like localhost:8181/mfc/city/getAllCities that should give me all the cities as json
Could you tell me what i should add?
City.java
package com.mfc.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="city")
public class City{
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
int id;
#Column(name="city_name")
String cityName;
#Column(name="city_id")
int cityId;
public City() {
super();
}
public City(int id, String cityName, int cityId) {
super();
this.id = id;
this.cityName = cityName;
this.cityId = cityId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
}
CityController.java
package com.mfc.admin.controller;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
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.RestController;
import com.mfc.admin.service.CityService;
import com.mfc.entity.City;
#RestController
#RequestMapping("/city")
public class CityController {
private static final Logger logger = LogManager.getLogger(CityController.class);
#Autowired
CityService cityService;
#RequestMapping(value="/getAllCities", method=RequestMethod.GET, headers = "Accept=application/json")
public List getCities() {
logger.trace("CityController: getAllCities begins");
List listOfCities = cityService.getAllCities();
logger.trace("CityController: getAllCities ends");
return listOfCities;
}
#RequestMapping(value="/getCity/{id}", method=RequestMethod.GET, headers = "Accept=application/json")
public City getCityById(#PathVariable int id) {
return cityService.getCity(id);
}
}
CityService.java
package com.mfc.admin.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.mfc.entity.City;
import com.mfc.repository.CityRepository;
#Service("cityService")
public class CityService {
#Autowired
CityRepository cityDTO;
#Transactional
public List getAllCities() {
return cityDTO.getAllCities();
}
#Transactional
public City getCity(int id) {
return cityDTO.getCity(id); // getCity is red here, there is mistake i guess
}
}
CityRepository.java
package com.mfc.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.mfc.entity.City;
public interface CityRepository extends JpaRepository<City, Integer>{
List getAllCities();
City getCity();
}
In the CityService you call the CityRepository like this
return cityDTO.getCity(id); // getCity is red here, there is mistake i guess
But no such method is defined in the CityRepository. Try using this line return cityDTO.findById(id).get();
You can't see the method findById(Integer id) in the CityRepository, but it is there, because the CityRepository extends JpaRepository<City, Integer>. Find some Spring Data tutorial to know what's really going on in here, long story short the Spring Data is able to generate a lot of standard methods for you.
The method cityDTO.findById(id) returns Optional<City>, not City. To get the instance of City, just add '.get()' method, as it is in the example. It should work for you if city exists in the database. For proper work with Optional find some tutorial. It is a wrapper of an object that may or may not be present, detailed explanation is out of the scope of this answer.
maybe you can try to set up message converter manualy, google MappingJackson2HttpMessageConverter and you'll know what to do.
How can I insert id manually for this JPA entity in Spring boot? I don't want the id to be autogenerated. I tried sending a POST request using postman sending this JSON object to a RestController:
{
"id":"1",
"name":"New York"
}
I get an error saying that I should manually assing id. Why it is not taking the id that I'm passing in the request?
The Code:
Entity
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class City{
#Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long Id) {
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Controller:
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CityService{
private CityService cityService;
#Autowired
public void setCityService(CityService CityService) {
this.CityService = CityService;
}
#RequestMapping(method=RequestMethod.POST, value="/cities")
public void cities(#RequestBody City city){
cityService.save(city);
}
}
Service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
#Service
public class CityService {
private CityRepository cityRepository;
#Autowired
public CityServiceImpl(CityRepository cityRepository) {
this.cityRepository= cityRepository;
}
#Override
public void saveCity(City city) {
CityRepository.save(city);
}
}
Theres probably something wrong with your setters. Try generating them again.
There was an old table with a different structure in the database. There was no error in the code.
I'm trying to configure mongodb auditing in my spring boot app, and I having this error when trying to persist my domain class:
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class com.example.hateoasapi.domain.Post!
Docs from here https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#auditing says that all this configs enough, but I don't know why it doesn't work in my project. Could someone help me?
My mongodb config class:
package com.example.hateoasapi.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.core.MongoTemplate;
import com.mongodb.MongoClient;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import java.util.Collection;
import java.util.Collections;
#Configuration
#EnableMongoAuditing
#EnableMongoRepositories(value = "com.example.hateoasapi.repository")
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database}")
private String databaseName;
#Value("${spring.data.mongodb.host}")
private String databaseHost;
#Value("${spring.data.mongodb.port}")
private Integer databasePort;
#Override
protected String getDatabaseName() {
return this.databaseName;
}
#Bean
#Override
public MongoClient mongoClient() {
return new MongoClient(databaseHost, databasePort);
}
#Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoClient(), databaseName);
}
#Override
protected Collection<String> getMappingBasePackages() {
return Collections.singleton("com.example.hateoasapi.domain");
}
}
AuditorAware implementation:
package com.example.hateoasapi.config;
import com.example.hateoasapi.domain.User;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Optional;
#Component
public class SecurityAuditor implements AuditorAware<User> {
#Override
public Optional<User> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return Optional.of((User) authentication.getPrincipal());
}
}
And my domain class:
package com.example.hateoasapi.domain;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import org.joda.time.DateTime;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.io.Serializable;
import java.util.List;
import com.example.hateoasapi.controller.*;
#Getter
#Setter
#ToString
#Document
public class Post extends ResourceSupport implements Serializable {
#Id
#Field(value = "_id")
private String objectId;
#DBRef
private List<Comment> comments;
#DBRef
private User author;
#NotBlank
private String body;
#NotBlank
private String title;
private String categoryId;
#NotEmpty(message = "Tags cannot be empty")
private List<PostTag> tags;
#CreatedDate
private DateTime createdDate;
#LastModifiedDate
private DateTime lastModifiedDate;
#CreatedBy
private User createdBy;
private Long views;
private List<PostRating> likes;
private List<PostRating> dislikes;
#JsonCreator
public Post() {}
public Post(String title, String body) {
this.body = body;
this.title = title;
}
public Post(User author, String body, String title, String categoryId, List<PostTag> tags) {
this.author = author;
this.body = body;
this.title = title;
this.categoryId = categoryId;
this.tags = tags;
}
public void addLinks() {
this.add(linkTo(methodOn(PostController.class).getAllPosts(null)).withSelfRel());
}
}
I solved this issue with the next configuration:
#Configuration
#EnableMongoRepositories(basePackages = "YOUR.PACKAGE")
#EnableMongoAuditing
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private Integer port;
#Value("${spring.data.mongodb.database}")
private String database;
#Override
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
#Override
protected String getDatabaseName() {
return database;
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
}
#Bean
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoDbFactory(mongoClient(), database);
}
}
just add the bean for MongoTemplate with the constructor of MongoTemplate(MongoDbFactory mongoDbFactory, #Nullable MongoConverter mongoConverter)
Quoting from JIRA ticket
You need to pipe the MappingMongoConverter that's available in the environment into MongoTemplate as well, i.e. use new MongoTemplate(dbFactory, converter). The constructor you use is for convenience, one-off usages. We usually recommend to use AbstractMongoConfiguration in case you'd like to customize anything MongoDB specific as this makes sure the components are wired together correctly.
More specifically, you need to inject pre-configured MappingMongoConverter or if you need to use your own converter, at least use pre-configured MongoMappingContext.
I had this problem also with spring boot 2.2
I had both #EnableMongoRepositories and #EnableMongoAuditing as configuration and i got the error Couldn't find PersistentEntity for type class
the problem in my case was the structure of the packages: Application class was a level lower than part of my model that used auditing.
I found on many forum posts that the 2 annotations are not compatible together in spring 2.2, but after restructuring the packages I was able to use both with success in spring boot 2.2
If you use the last version of Spring boot (2.0) and Spring Data, #EnableMongoAuditing
#EnableMongoRepositories are not compatible. It's the same with EnableReactiveMongoRepositories annotation.
If you want to enable mongo auditing, you need to remove your MongoConfig class, use config file to define your mongodb connection and everything will work.
If you use the last version of Spring boot (2.0) and Spring Data, #EnableMongoAuditing and #EnableMongoRepositories, try remove #EnableMongoRepositories. It should be working just this sample project - https://github.com/hantsy/spring-reactive-sample/tree/master/boot-data-mongo