SnippetException: Cannot document response fields as the response body is empty - spring

Here is my Controller:
#PostMapping("post")
#PreAuthorize("hasAuthority('WRITE')")
public ResponseEntity<?> createPost(#RequestBody PostEntity postEntity) {
return new ResponseEntity<>(postService.createPost(postEntity), HttpStatus.CREATED);
}
Service :
#Override
public Post createPost(PostEntity postEntity) {
return postFactory.buildPost(postEntityRepository.save(postEntity));
}
//Post is Immutable class here
public Post buildPost(PostEntity entity) {
return new Post.Builder()
.groupId(entity.getGroupEntity().getGroupId())
.postedBy(entity.getPostedBy().getUsername())
.postType(entity.getType())
.postMedia(entity.getPostMedia())
.postId(entity.getPostId())
.build();
}
Here is my mockMvc:
#BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}",
preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
Here is my Test:
this.mockMvc.perform(post("/api/post")
.contentType(MediaTypes.HAL_JSON)
.contextPath("/api")
.content(this.objectMapper.writeValueAsString(postEntity)))
.andExpect(status().isCreated())
.andDo(
document("{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(describeCreatePostRequest()),
responseFields(describePostEntityResult())
));
Here is Post call:
#Value.Immutable
#JsonSerialize(as = ImmutablePost.class)
#JsonDeserialize(as = ImmutablePost.class)
#JsonInclude(JsonInclude.Include.NON_NULL)
#Relation(value = "post", collectionRelation = "posts")
public interface Post {
Long getPostId();
String getPostType();
String postedBy();
#Nullable
PostMedia postMedia();
Long groupId();
class Builder extends ImmutablePost.Builder {}
}
PostEntity #Entity class here is json format:
{
"type" : "text",
"postedBy" : {
"username": "sandeep"
},
"postMedia" : {
"mediaType": "text",
"mediaUrl": "null",
"content": "Hi this is testing media content",
"createdAt": 1234,
"updatedAt": 689
},
"groupEntity": {
"groupId": 4
}
}
And Post entity class:
#Entity
#Table(name = "POST")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class PostEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "POST_ID")
private Long postId;
#Column(name = "POST_TYPE")
private String type;
#ManyToOne
#JoinColumn(name = "username", referencedColumnName = "username")
private User postedBy;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "post_media_id", referencedColumnName = "id")
private PostMedia postMedia;
#ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
#JoinColumn(name = "GROUP_ID", referencedColumnName = "GROUP_ID")
private GroupEntity groupEntity;
public PostEntity() {
}
}
I have tried
objectMapper.readValue(objectMapper.writeValueAsString(postEntity), ImmutablePost.class);
As well. but still its not working, I am facing same exception:
org.springframework.restdocs.snippet.SnippetException: Cannot document response fields as the response body is empty
at org.springframework.restdocs.payload.AbstractFieldsSnippet.verifyContent(AbstractFieldsSnippet.java:191)
at org.springframework.restdocs.payload.AbstractFieldsSnippet.createModel(AbstractFieldsSnippet.java:147)
at org.springframework.restdocs.snippet.TemplatedSnippet.document(TemplatedSnippet.java:78)
at org.springframework.restdocs.generate.RestDocumentationGenerator.handle(RestDocumentationGenerator.java:191)

The problem is with the "PostMedia" setters "PostMedia" and "GroupEntity". You need to accept strings in the setters parameter and convert them to the corresponding entity. Expecting the setter's arguments as the custom entity type is making the problem.
For example:
public class MyModel {
private CustomEnum myEnum;
public CustomEnum getMyEnum() {
return myEnum;
}
public void setMyEnum(String enumName) {
this.myEnum = CustomEnum.valueOf(enumName);
}
}

Related

How to use a DTO class passing a Embedded class for verification?

How can I tackle a #Embedded class inside of a Model class?
I'm trying to use a DTO to filter and check the requests being sent to the POST mapping.
I want to check, let's say, if the fullName is not blank.
(request body below)
DentistModel.java
public class DentistModel {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "dentist_id", updatable = false, nullable = false)
#Type(type = "org.hibernate.type.UUIDCharType")
private UUID id;
#Column(nullable = false, unique = true, length = 10)
private String croNumber;
#Embedded
private Person person;
}
DentistDto.java
public class DentistDto {
#NotBlank
private String croNumber;
}
DentistController.java
#PostMapping
public ResponseEntity<Object> saveDentist(#RequestBody #Valid DentistDto dentistDto) {
if (dentistService.existsByCroNumber(dentistDto.getCroNumber())) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("Conflict: CRO number is already in use.");
}
var dentistModel = new DentistModel();
BeanUtils.copyProperties(dentistDto, dentistModel);
return ResponseEntity.status(HttpStatus.CREATED).body(dentistService.save(dentistModel));
}
Dentist request body
{
"croNumber":"12345",
"person": {
"fullName": "John",
"birthDate": "2000-01-27",
"cpfNumber": "11408247910",
"telephoneNumber": "47996034002",
"emailAddress": "john#gmail.com",
"address": {
"mainAddress": "Rua Amoroso Costa",
"numberAddress": "171",
"neighborHood": "Jardim das Américas",
"complementInfo": "Casa",
"zipCode": "81530-150"
}
}
}

Why am i getting nulls in parameters inside json?

this is small spring-boot project
I am unable to get users parameteres
I still can create other users in db using POST method, but when i am trying get list of all users or only one by id, i am getting json with nulls in users parameters.
I want to get this from getUsers method
{
"id": 1,
"name": testName,
"password": testPassword,
"email": testEmail,
"phoneNumber": testPhoneNumber,
"status": USER
},
Instread of this i am getting
{
"id": null,
"name": null,
"password": null,
"email": null,
"phoneNumber": null,
"status": null
},
Here is my entity class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",nullable = false)
private Integer id;
#Column(name = "name",nullable = false)
private String name;
#Column(name = "password",nullable = false)
private String password;
#Column(name = "email",nullable = false)
private String email;
#Column(name = "phoneNumber",nullable = false)
private String phoneNumber;
#Column(name = "status",nullable = false)
private Status status;
}
My controller
#Controller
#RequestMapping
#RequiredArgsConstructor
#Validated
public class UserController {
private final UserService userService;
#Operation(description = "Getting list of users")
#GetMapping(
value = "/v1/users",
produces = {"application/json"}
)
public ResponseEntity<List<UserDto>> getUsers() {
return ResponseEntity.ok(userService.getUsers());
}
My service
#Service
#RequiredArgsConstructor
#Data
public class UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
public List<UserDto> getUsers() {
return StreamSupport.stream(userRepository.findAll().spliterator(), false)
.map(userMapper::userToUserDto).collect(Collectors.toList());
}

Empty results returned using a many to many relation

I working with Spring and I have developed two entities: Role and User with a ManyToMany relation between them. The related classes of my project are the following ones:
Entities
#Entity
#Table(name = "UTILISATEURS")
public class Utilisateur {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
private String email;
#ManyToMany(mappedBy = "utilisateurs")
private Set<Role> roles = new HashSet<Role>();
public Utilisateur() {
super();
}
public void addRole(Role role) {
this.roles.add(role);
}
// Getters and setters
}
#Entity
#Table(name = "ROLES")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UTILISATEURS_ROLES", joinColumns = #JoinColumn(name = "ROLE_ID"), inverseJoinColumns = #JoinColumn(name = "UTILISATEUR_ID"))
private Set<Utilisateur> utilisateurs = new HashSet<Utilisateur>();
public Role(String name) {
this.name = name;
}
public void addUtilisateurs(Utilisateur user) {
this.utilisateurs.add(user);
}
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UTILISATEURS_ROLES", joinColumns = #JoinColumn(name = "ROLE_ID"), inverseJoinColumns = #JoinColumn(name = "UTILISATEUR_ID"))
public Set<Utilisateur> getUtilisateurs() {
return utilisateurs;
}
// Getters and setters
}
Repositories:
public interface RoleRepository extends JpaRepository<Role, Long> {
}
public interface UtilisateurRepository extends JpaRepository<Utilisateur, Long> {
}
Main application:
#SpringBootApplication
public class ContactsApplication implements CommandLineRunner {
#Autowired
UtilisateurRepository utilisateurRepository;
#Autowired
RoleRepository roleRepository;
public static void main(String[] args) {
SpringApplication.run(ContactsApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
Utilisateur user1 = new Utilisateur();
user1.setEmail("jl#mymail1.com");
user1.setPassword("mypass1");
user1.setUsername("myUser1");
Role role1 = new Role("ADMIN");
roleRepository.save(role1);
role1.addUtilisateurs(user1);
Set<Role> userRole1 = new HashSet<Role>();
userRole1.add(role1);
user1.addRole(role1);
utilisateurRepository.save(user1);
Utilisateur user2 = new Utilisateur();
user2.setEmail("jl#mymail2.com");
user2.setPassword("mypass2");
user2.setUsername("myUser2");
Role role2 = new Role("USER");
roleRepository.save(role2);
role2.addUtilisateurs(user2);
Set<Role> userRole2 = new HashSet<Role>();
userRole2.add(role2);
user2.setRoles(userRole2);
user2.addRole(role2);
utilisateurRepository.save(user2);
}
}
Controller:
#RestController
#CrossOrigin("*")
public class UtilisateurRestService {
#Autowired
private UtilisateurRepository utilisateurRepository;
#RequestMapping(value = "/users", method = RequestMethod.GET)
public List<Utilisateur> getUtilisateurs() {
return utilisateurRepository.findAll();
}
}
When I invoke the endpoint http://localhost:8080/users, I receive the following results (with empty roles):
[
{
"id": 1,
"username": "myUser1",
"password": "mypass1",
"email": "jl#mymail1.com",
"roles": []
},
{
"id": 2,
"username": "myUser2",
"password": "mypass2",
"email": "jl#mymail2.com",
"roles": []
}
]
Have you any idea what my roles arrays are empty? It should contains one role(admin) for the first and role(user) for the second.

POST request with Many-to-many relationship in Spring Data REST

I'm trying to add a movie to MySQL database, here's my database schema:
Movie(id, name)
Genre(id, name)
Movie_genre(id_movie, id_genre)
And here's my model classes:
Movie.ts
public class Movie {
private Short id;
private String name;
private List<MovieGenre> movieGenres;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
public Short getId() {
return id;
}
public void setId(Short id) {
this.id = id;
}
#Column(name = "name", nullable = false, length = 100)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "movie")
public List<MovieGenre> getMovieGenres() {
return movieGenres;
}
public void setMovieGenres(List<MovieGenre> movieGenres) {
this.movieGenres = movieGenres;
}
}
Genre.ts
public class Genre {
private Short id;
private String name;
private List<MovieGenre> movieGenres;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
public Short getId() {
return id;
}
public void setId(Short id) {
this.id = id;
}
#Column(name = "name", nullable = false, length = 15)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "genre")
#JsonIgnore
public List<MovieGenre> getMovieGenres() {
return movieGenres;
}
public void setMovieGenres(List<MovieGenre> movieGenres) {
this.movieGenres = movieGenres;
}
}
MovieGenre.ts ( that model stands for the generated table )
public class MovieGenre {
private MovieGenrePK id;
private Movie movie;
private Genre genre;
#EmbeddedId
#JsonIgnore
public MovieGenrePK getId() {
return id;
}
public void setId(MovieGenrePK id) {
this.id = id;
}
#MapsId("movieId")
#ManyToOne
#JoinColumn(name = "movie_id", referencedColumnName = "id", nullable = false)
#JsonIgnore
public Movie getMovie() {
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
#MapsId("genreId")
#ManyToOne
#JoinColumn(name = "genre_id", referencedColumnName = "id", nullable = false)
public Genre getGenre() {
return genre;
}
public void setGenre(Genre genre) {
this.genre = genre;
}
}
and because we have composite key in the last model, we need a class for that :
public class MovieGenrePK implements Serializable {
private Short movieId;
private Short genreId;
#Column(name = "movie_id", nullable = false)
public Short getMovieId() {
return movieId;
}
public void setMovieId(Short movieId) {
this.movieId = movieId;
}
#Column(name = "genre_id", nullable = false)
public Short getGenreId() {
return genreId;
}
public void setGenreId(Short genreId) {
this.genreId = genreId;
}
}
So I'm trying to add a movie with genres by making a post request, first i made a post request for adding a movie and another one for adding a genre, that's works fine, now i need to associate a genre to a movie.
I tried the following:
I made a POST request to the following endpoint: http://localhost:8080/api/movieGenres with application/json header and with the following body:
{
"movie": "http://localhost:8080/api/movies/6",
"genre": "http://localhost:8080/api/genres/1"
}
but i got the error:
{
"timestamp": "2018-08-22T21:10:30.830+0000",
"status": 500,
"error": "Internal Server Error",
"message": "NullPointerException occurred while calling setter of com.movies.mmdbapi.model.MovieGenrePK.genreId; nested exception is org.hibernate.PropertyAccessException: NullPointerException occurred while calling setter of com.movies.mmdbapi.model.MovieGenrePK.genreId",
"path": "/api/movieGenres"
}
You have to instatiate MovieGenrePK, change:
public class MovieGenre {
private MovieGenrePK id;
}
to
public class MovieGenre {
private MovieGenrePK id = new MovieGenrePK();
}

Spring Data rest how to perform CRUD on #manytomany relation ,composite table with extra column

I am unable to perform CRUD via json POST from restful client Postman on Composite table having extra column .I am using Spring boot ,spring data rest and spring JPA.
I have 3 tables in data base
-user
-competency
-user_competency (join/composite table with extra column)
Here are my classes
User
#Entity
#Table(name = "\"user\"", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "userId")
public class User implements java.io.Serializable {
private Long userId;
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.ALL}, mappedBy = "user")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
Competency
#Entity
#Table(name = "competency", schema = "public")
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "competencyId")
public class Competency implements java.io.Serializable {
private Long competencyId;
private Set<UserCompetency> userCompetencies = new HashSet<UserCompetency>(0);
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "competency_id", unique = true, nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "competency")
public Set<UserCompetency> getUserCompetencies() {
return this.userCompetencies;
}
public void setUserCompetencies(Set<UserCompetency> userCompetencies) {
this.userCompetencies = userCompetencies;
}
}
UserCompetency
#Entity
#Table(name = "user_competency", schema = "public")
#JsonIdentityInfo(
generator =ObjectIdGenerators.IntSequenceGenerator.class,
property = "id")
public class UserCompetency implements java.io.Serializable {
private UserCompetencyId id;
private Level level;
private User user;
private Competency competency;
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "competencyId", column = #Column(name = "competency_id", nullable = false)),
#AttributeOverride(name = "userId", column = #Column(name = "user_id", nullable = false)) })
public UserCompetencyId getId() {
return this.id;
}
public void setId(UserCompetencyId id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "level_id")
public Level getLevel() {
return this.level;
}
public void setLevel(Level level) {
this.level = level;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
public User getUser() {
return this.user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
public Competency getCompetency() {
return this.competency;
}
public void setCompetency(Competency competency) {
this.competency = competency;
}
}
UserCompetencyId
#Embeddable
public class UserCompetencyId implements java.io.Serializable {
private Long competencyId;
private Long userId;
public UserCompetencyId() {
}
public UserCompetencyId(Long competencyId, Long userId) {
this.competencyId = competencyId;
this.userId = userId;
}
#Column(name = "competency_id", nullable = false)
public Long getCompetencyId() {
return this.competencyId;
}
public void setCompetencyId(Long competencyId) {
this.competencyId = competencyId;
}
#Column(name = "user_id", nullable = false)
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof UserCompetencyId))
return false;
UserCompetencyId castOther = (UserCompetencyId) other;
return (this.getCompetencyId() == castOther.getCompetencyId()) && (this.getUserId() == castOther.getUserId());
}
}
Suppose i have already record in User and Competency tables and i want to assocaite both i am trying to post like this ,but it give me error of 405 Method Not Allowed.
help required ,what should be structure of json to be posted User will already exist and competency will might exist or new can be added and associated with existing user.
With this code I was able to post a new relation:
UserCompetency.class
#Entity
#Table(name = "user_competency")
#IdClass(UserCompetencyId.class)
public class UserCompetency implements java.io.Serializable {
#Id #ManyToOne
#JoinColumn(name = "competency_id", nullable = false, insertable = false, updatable = false)
private Competency competency;
#Id #ManyToOne
#JoinColumn(name = "user_id", nullable = false, insertable = false, updatable = false)
private User user;
UserCompetencyId.class
public class UserCompetencyId implements java.io.Serializable {
private Long competency;
private Long user;
public UserCompetencyId() {
}
public UserCompetencyId(Long competency, Long user) {
this.competency = competency;
this.user = user;
}
UserCompetencyRepository.class
public interface UserCompetencyRepository extends JpaRepository<UserCompetency, UserCompetencyId> {
}
POST http://localhost:8080/userCompetencies
{
"competency": "/competencies/2"
, "user": "/user/4"
}
Apparently there seems to be no "natural/easy" way to get what you want. But there is a promissing project for integrating embeddables by extending the serialization process: https://github.com/gregturn/embeddable-spring-data-rest
UserCompetencyIdJacksonModule, UserCompetencyIdSerializer, ..
Then you should be able PATCH (not POST) your JSON from above.

Resources