Using the following #NodeEntity
#NodeEntity
public class Person extends BasePersistenceObject {
#GraphId
Long id;
String fullName;
#Indexed(unique=true)
String email;
String passwordHash = null;
#JsonIgnore
public String getPasswordHash() {
return passwordHash;
}
...
}
I'm still seeing the passwordHash in the JSON Response from the following controller method:
#RequestMapping(value = "/login", method=RequestMethod.POST)
public #ResponseBody Person login(#RequestBody Map<String, String> credentials, HttpServletRequest request) throws AuthorizationException {
String email = credentials.get("email");
String password = credentials.get("password");
String ipAddress = request.getRemoteAddr();
return authService.authenticate(email, password, ipAddress);
}
Spring Data Neo4J has nothing to do with JSON serialization, that's the responsibility of Spring MVC and Jackson or Gson, depending on what you use. So make sure, that Jackson is used for JSON serialization, otherwise the annotation wouldn't work.
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
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 an abstract class "Agent"
and 3 other subclasses "Developer", "Support" and "Admin"
Here is the code source of "Agent" :
#Entity
#Table(name = "agents")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "role", discriminatorType =
DiscriminatorType.STRING, length = 3)
public abstract class Agent implements Serializable {
#Id
#GeneratedValue
private int id;
private String name;
private String lastName;
.........}
The code source of "Developer" classe
#Entity
#DiscriminatorValue("dev")
public class Developer extends Agent {
/*------------------- constructors -------------------*/
public Developer() {
super();
}
public Developer(String name, String lastName, ....) {
super(name, lastName, ...);
}
}
The rest of the classes "Admin", "Supprort" has the same form.
Here is my controller code Admin controller :
#Controller
public class AdminController {
/*------- attributs -------*/
#Autowired
#Resource(name = "admin")
private IAdmin iAdmin;
#Autowired
private AgentValidator agentValidator;
........
#RequestMapping(value = "/admin/save/developer", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Developer developer, BindingResult result) {
Agent admin = iAdmin.profile(Integer.parseInt(admin_id));
developer.setConfirmPassword(confirmPassword);
agentValidator.validate(developer, result);
if (result.hasErrors()) {
model.addAttribute("action", action);
return "formAgents";
}
if (action.equals("create")) {
iAdmin.createAgent(admin, developer);
} else {
iAdmin.updateAgent(admin, developer);
}
return "redirect:/admin/show/agents";
}
.......
As you see this function create and update the developer account, But i need to save all agents types [admin, developer, support], I try this :
public String createAgentAccount(Model model, ... , #ModelAttribute("agent") Agent developer, BindingResult result) {.....}
But i get this error :
Tue Aug 22 19:54:03 WEST 2017
There was an unexpected error (type=Internal Server Error, status=500).
Failed to instantiate [com.GemCrmTickets.entities.Agent]: Is it an abstract class?; nested exception is java.lang.InstantiationException
I know that is impossible to instanciate an abstract Class. I don't want to do a function for each type of agent, One for all will be the best solution. So i need your help please. And thank you.
Your answer is one word. Use Ad hoc polymorphism, which means you can have multiple methods of createAgentAccount, then in each of them call an other method to handle the details.
UPDATE
This is what I think you want
#RequestMapping(value = "/admin/save/developer", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Developer developer, BindingResult result) {
return createAgentAccount(model, admin_id, confirmPassword, action, developer, result);
}
#RequestMapping(value = "/admin/save/support", method = RequestMethod.POST)
public String createAgentAccount(Model model, String admin_id, String confirmPassword, String action, #ModelAttribute("agent") Support support, BindingResult result) {
return createAgentAccount(model, admin_id, confirmPassword, action, support, result);
}
private String createAccount(Model model, String admin_id, String confirmPassword, String action, Agent agent, BindingResult result) {
Agent admin = iAdmin.profile(Integer.parseInt(admin_id));
agent.setConfirmPassword(confirmPassword);
agentValidator.validate(agent, result);
if (result.hasErrors()) {
model.addAttribute("action", action);
return "formAgents";
}
if (action.equals("create")) {
iAdmin.createAgent(admin, agent);
} else {
iAdmin.updateAgent(admin, agent);
}
return "redirect:/admin/show/agents";
}
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 have the following example:
This is the request body:
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
//... getter and setters
}
This is the Controller:
#RequestMapping(value = {"/login"}, method = RequestMethod.POST)
#ResponseBody
public LoginResponse login(#RequestBody(required = true) UserLoginData loginData){
//... some code
}
This is how I invoke the service:
POST /login
{"username":"neuquino"}
I expect that Spring returns a HTTP 400 BAD REQUEST error, because password is missing. But instead of that, it returns a HTTP 500 INTERNAL SERVER error with the following stacktrace:
java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:948) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
...
How can I specify to Spring that username and password are required fields in request body?
#Bart's answer was very useful to find my final solution:
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#NotBlank
private String username;
#NotNull
#NotBlank
private String password;
//... getter and setters
}
On my Controller I have:
public LoginResponse login(
#RequestBody(required = true) #Valid UserLoginData loginData){
//... login code
}
Until here is very similar, but it is clearer because the controller's method does not have the error validation. Instead of that, I used another class with the ControllerAdvice annotation
#ControllerAdvice
public class RestErrorHandler {
private MessageSource messageSource;
#Autowired
public RestErrorHandler(#Qualifier("messageSource") MessageSource messageSource) {
this.messageSource = messageSource;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationError processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return this.processFieldErrors(fieldErrors);
}
private ValidationError processFieldErrors(List<FieldError> fieldErrors) {
ValidationError dto = new ValidationError();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = this.resolveLocalizedErrorMessage(fieldError);
dto.getErrors().put(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = this.messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
Now my service is returning this:
{
"errors":{
"country":"country cannot be null"
}
}
I hope it helps someone else.
To get this solution I also used what is written in this post.
If the password is missing it will not be set when the UserLoginData object is created. It will not check if the value is valid or anything. If you need to validate your login data use proper validation.
You could use the annotations in the hibernate validator package for declarative validation e.g.
public class UserLoginData
implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#NotBlank
private String username;
#NotNull
#NotBlank
private String password;
//... getter and setters
}
Your method could then be written as (note the #Valid annotation):
public LoginResponse login(
#RequestBody(required = true) #Valid UserLoginData loginData,
BindingResult result,
HttpServletResponse response){
if (result.hasErrors()) {
// Validation problems!
response.sendError(400, "Bad login data");
}
}