I have 3 different method in controller for get requests.
-the 1st one to get a user by id with a path variable:
#GetMapping(path="/{id}")
public ResponseEntity<UserInfoDTO> getUserById(#PathVariable Long id)
The 2nd gets a user based on the username parameter:
public ResponseEntity<UserInfoDTO> getUserByUsername(#RequestParam String username)
And finally another one to get all users
public ResponseEntity<List<UserInfoDTO>> getAllUsers()
What should be the #GetMapping for the 2nd and 3rd method?
For exemple #GetMapping for all users and #GetMapping(path="/") for a user by username?
Or whatever...
Thanks.
Defining the Mappings purely depends on the context of your application and its usecases.
We can define a context prefixed by users and modified mappings are show in the snippet below and at the time of invocation it can be called like mentioned in the comments,
#GetMapping(path="/users/")
public ResponseEntity<UserInfoDTO> getUserByUsername(#RequestParam String username) {
}
// GET: <protocol>://<hostUrl>/users?username=<username>
#GetMapping(path="/users")
public ResponseEntity<List<UserInfoDTO>> getAllUsers() {
}
// GET: <protocol>://<hostUrl>/users
#GetMapping(path="/users/{id}")
public ResponseEntity<UserInfoDTO> getUserById(#PathVariable Long id)
// GET: <protocol>://<hostUrl>/users/<userid>
For example, optional username param:
#GetMapping(path = "/")
public ResponseEntity<?> getUserByUsername(#RequestParam(required = false) final String username) {
if (username != null) {
// http://localhost:8080/?username=myname
return new ResponseEntity<>(new UserInfoDTO("by username: " + username), HttpStatus.OK);
} else {
// http://localhost:8080/
return getAllUsers();
}
}
private ResponseEntity<List<UserInfoDTO>> getAllUsers() {
return new ResponseEntity<>(List.of(new UserInfoDTO("user1-of-all"), new UserInfoDTO("user2-of-all")),
HttpStatus.OK);
}
public static class UserInfoDTO {
public UserInfoDTO(final String name) {
this.name = name;
}
private final String name;
public String getName() {
return name;
}
}
Related
I am trying to update an Entity by using spring boot 2.5.3 in the controller method.
http://localhost:5000/api/v1/student/1
with the following payload.
{
"name":"abc",
"email":"abc#email.com",
"dob":"2000-06-14"
}
These values are not updated. They are getting null values when I inspected them using a debugger.
Here is my controller method.
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestParam(required = false) String name, #RequestParam(required = false) String email) {
Student savedStudent = studentService.updateStudent(id, name, email);
return ResponseEntity.ok(savedStudent);
}
Email and name are optional.
In debugger: name:null,email:null. Why are they getting null values?
What is the correct way to pass values from the controller?
#Transactional
// We are not using any query from the repository because we have the service method with transactional annotation.
public Student updateStudent(Long studentId, String name, String email) {
Student student = studentRepository.findById(studentId).orElseThrow(()->new EntityNotFoundException("Student with id " + studentId + " does not exists."));
if (name!= null && name.length()>0 && !Objects.equals(name,student.getName())){
student.setName(name);
}
if (email!= null && email.length()>0 && !Objects.equals(email,student.getEmail())){
Optional<Student> optionalStudent = studentRepository.findStudentByEmail(email);
if (optionalStudent.isPresent()){
throw new IllegalStateException("Email is already taken");
}
student.setEmail(email);
}
System.out.println(student);
Student savedStudent= studentRepository.save(student);
return savedStudent;
}
{
"name":"abc",
"email":"abc#email.com",
"dob":"2000-06-14"
}
This is not a request parameter but the request body. You need to create a class and use #RequestBody annotation.
#Data
public class UpdateStudentRequest {
private String id;
private String name;
private String email;
}
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestBody UpdateStudentRequest request) {
Student savedStudent = studentService.updateStudent(
request.getId(), request.getName(), request.getEmail());
return ResponseEntity.ok(savedStudent);
}
If you want to send the request parameters as... URL parameters:
http://localhost:5000/api/v1/student/1?name=abc&email=abc#email.com
You aren't sending it as a param (after ?).
http://localhost:5000/api/v1/student/1?name=John Could do the trick.
Since you are POSTing an HTTP request with a content body (being in JSON in your case), you need to map the body using the #RequestBody annotation:
#PutMapping(path = "/{id}")
public ResponseEntity<?> updateStudent(#PathVariable("id") Long id, #RequestBody StudentDTO student) {
Student savedStudent = studentService.updateStudent(
id, student.getName(), student.getEmail());
return ResponseEntity.ok(savedStudent);
}
The StudentDTO would be a lightweight type reflecting your input payload:
public class StudentDTO {
private String name;
private String email;
private String dob;
// setters and getters
}
Otherwise, to keep your RestController signature and use the #RequestParametrized fields, you should send a request of following shape:
http://localhost:5000/api/v1/student/1?name=abc&email=abc#email.com&dob=2000-06-14
My controller:
#PostMapping
public ResponseEntity<UserCreateResponse> createUser(#RequestBody #Valid UserCreateRequest userDto,
BindingResult result)
throws InvalidRequestException {
if (result.hasErrors()) {
throw new InvalidRequestException("Request parameter validation failed");
} else {
return ResponseEntity.ok(userService.createUser(userDto));
}
}
Service:
public UserCreateResponse createUser(UserCreateRequest userDto) {
return convertEntityToDto(userRepository.insert(convertDtoToEntity(userDto)));
}
private User convertDtoToEntity(UserCreateRequest userDto) {
return modelMapper.map(userDto, User.class);
}
private UserCreateResponse convertEntityToDto(User user) {
return modelMapper.map(user, UserCreateResponse.class);
}
And the model is :
#Getter
#Setter
#Document("User")
public class User {
#Id
private String id;
#Indexed(unique = true)
private String userName;
private String name;
private String surname;
private String job;
}
Repository is just a class extending MongoRepository.
When I try to insert 2 User with same userName via postman post request, it is adding 2 exactly same item to db even if I specified #Indexed(unique = true) to userName field. Why does this happen and how can I fix it on Java side without breaking indexing function on the field(I want to index userName field to find faster)
In my Spring Boot application I am creating a REST API, which is calling some other external REST API. I created User class, which is a object that is received by my Rest API downloaded from the external API. My user model looks like:
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String fullName;
private String department;
#JsonGetter("fullName")
public String getFullName() {
return fullName;
}
#JsonSetter("full_name")
public void setFullName(String fullName) {
this.fullName = fullName;
}
#JsonGetter("department")
public String getDepartment() {
return department;
}
#JsonSetter("department")
public void setDepartment(String department) {
this.department = department;
}
}
I am using JsonGetter and JsonSetter properties, because I would like to have my json properties in response returned in camelCase, but the properties given in external API are returned with underscore:
External API Response:
{
"full_name": "User A",
"department": "A",
}
My API Response:
{
"fullName": "User A",
"department": "A",
}
And everything seems to be working fine (hitting my API with Postman gives proper responses) until I started to create some Http request tests. In tests I receive assertion error that fullName property is null, while doing the same request in postman is responding with proper responses.
My test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void shouldReturnUserFullName() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/users/a",
User.class)).extracting(User::getFullName)
.contains("User A");
}
}
My controller method:
#GetMapping("users/{name}")
public ResponseEntity<User> getSpecificUserByName(#PathVariable("name") String name) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<User> response = restTemplate.exchange(createUriString(name), HttpMethod.GET, entity, User.class);
return response;
}
Test result:
java.lang.AssertionError:
Expecting:
<[null]>
to contain:
<["User A"]>
but could not find:
<["User A"]>
I would appreciate any help with this issue :)
#JsonSetter("full_name") expects your API response to contain a property full_name during deserialzation. Since #JsonGetter("fullName") converts full_name to fullName, field private String fullName; is never set.
You should change #JsonSetter("full_name") to #JsonSetter("fullName").
Let us take an example
Suppose your REST API returns below Object of User class
User reponse = new User();
response.setFullName("User A");
response.setDepartment("A");
So, when we call your REST API, the JSON response would look like as below
{
"fullName":"User A",
"department":"A"
}
Now, When you pass this JSON to convert into User class, Jackson will look for methods with the name setFullName and setDepartment.
In your test case, something similar is happening,
for code
this.restTemplate.getForObject("http://localhost:" + port + "/users/a",User.class)
First, it calls your API to get the User object Serialized and then it Deserialized it to User class. While Deserializing, it looks for a method named
setFullName without any
#Setter
#JsonProperty
#JsonAlias
annotations
or will look for any setter method with
#Setter("fullName")
#JsonProperty("fullName"),
#JsonAlias("fullName")
but in your case, the fullName setter is treated as
public void setFull_name(String fullName) {
this.fullName = fullname;
}
So, setter for fullName is not found but since you marked your User class as
#JsonIgnoreProperties(ignoreUnknown = true)
hence any exception is not thrown but fullName for your Response JSON is ignored, so fullName is never set, which remains null and your Test case is failing.
So, either change your test case or mark your setter with
#JsonAlias("fullName")
annotation.
i.e. Your User class will look like as below
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String fullName;
private String department;
#JsonGetter("fullName")
public String getFullName() {
return fullName;
}
#JsonAlias({"fullName","full_name"})
public void setFullName(String fullName) {
this.fullName = fullName;
}
#JsonGetter("department")
public String getDepartment() {
return department;
}
#JsonSetter("department")
public void setDepartment(String department) {
this.department = department;
}
}
I have a Json like the following.
{"person":[{"name":"asd","age":"22"},{"name":"asd","age":"22"}]}
but it could also be:
{"person":[{"name":"asd","age":"22"},{"name":"asd","age":"22"}],"city":["NewYork"],"student":"false"}
How can I receive it in a Spring Boot Controller?
You should use #RequestBody annotation.
#RequestMapping("/api/example")
public String example(#RequestBody String string) {
return string;
}
Later, add some validations and business logic.
You can generate custom class with http://www.jsonschema2pojo.org/. Once generated you can expect your custom class instead of String.
For further instructions, I find this tutorial interesting.
You can receive the json like below, Spring Boot will convert your json into model(For example "Comment" model below) which you defined.
#RequestMapping(value = "/create", method = RequestMethod.POST)
public ResultModel createComment(#RequestBody Comment comment) {...}
1) You need to difine your rest controllers. Example
#Autowired
UserService userService;
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if (users.isEmpty()) {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}
2) Define your pojo: Example
public class User {
String name;
String age;
public User(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
3) Define a service
#Service
public class UserService {
public List<User> findAllUsers(){
// Those are mock data. I suggest to search for Spring-data for interaction with DB.
ArrayList<User> users = new ArrayList<>();
User user = new User("name", "5");
users.add(user);
return users;
}
}
You can follow this tutorial. If you want to just send a json message to a spring boot rest controller you can use a rest client like postman.
I created a filter with ContainerRequestFilter interface and try to assign custom roles that returning user entity.
#Override
public ContainerRequest filter(ContainerRequest request) {
User user = authenticate(request);
if (user != null) {
request.setSecurityContext(new Authorizer(user));
} else {
throw new WebApplicationException(400);
}
return request;
}
private User authenticate(ContainerRequest request) {
user = new User("erhan", "customRole");
return user;
}
public class Authorizer implements SecurityContext {
private User user;
private Principal principal;
public Authorizer(final User user) {
this.user = user;
this.principal = new Principal() {
public String getName() {
return user.username;
}
};
}
public Principal getUserPrincipal() {
return this.principal;
}
public boolean isUserInRole(String role) {
return (role.equals(user.role));
}
public boolean isSecure() {
return "https".equals(uriInfo.getRequestUri().getScheme());
}
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
}
public class User {
public String username;
public String role;
public User(String username, String role) {
this.username = username;
this.role = role;
}
}
Everything is fine with that filter, but when it goes to web service
#GET
#RolesAllowed({"customRole"})
#Path("/test")
public String getByType(#Context HttpHeaders headers,#Context SecurityContext sc,
#Context HttpServletRequest request) {
return null;
}
it reaches the webservice but when i change role,still reached the same webservice. How can i provide different custom roles in Jersey ?
Using Jersey 2 you can just register RolesAllowedDynamicFeature and secure your application in web.xml. Than you don't need custom SecurityContext implementation.
See Jersey custom SecurityContext on EJB jax-rs resource for details about that.
Try to put annotation to the class.
For me adding #PreMatching with #Provider works.
It seems that Provider annotation is mandatory in this case.
#PreMatching
#Provider
public class RequestFilter implements ContainerRequestFilter {
.....
}
If this does not work try to add this:
#Priority(Priorities.AUTHORIZATION)
Additionally you need to enables roles and register RolesAllowedDynamicFeature or use alternatives - check example 19.2