How to send Status Codes Along with my Custom Class Using Spring? - java

I am trying to make a log in system using spring. Problem is if username is not in the database I want to send a different status code and if username is in the database but password is wrong I want to send different status code. Because in my front end i am going to inform user using different alerts according to status code.
I cannot use HttpStatus.NOT_ACCEPTABLE or something like that because my controller is returning a User(my custom class). It will either return User or null.
#GetMapping("/users")
public User userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return null;
}
if(user.getPassword().equals(password)) {
return user;
} else {
return null;
}
}
Here I am trying to change status while returning nulls.

you can return ResponseEntity to meet your requirement
#GetMapping("/users")
public ResponseEntity<User> userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);
}
if(user.getPassword().equals(password)) {
return new ResponseEntity<>(user,HttpStatus.OK);
} else {
return new ResponseEntity<>(null,HttpStatus.FORBIDDEN);
}
}

Spring 5 introduced the ResponseStatusException class. We can create an instance of it providing an HttpStatus and optionally a reason and a cause:
#GetMapping(value = "/{id}") public Foo findById(#PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
} }
Maybe this is which you looking for?
Detail in https://www.baeldung.com/exception-handling-for-rest-with-spring#controlleradvice

Related

Hibernate not fetching object from repository

Im making a basic reset password functionality in my API but when i try to fetch the PasswordResetToken object from the repository hibernate doesnt fetch it and instead sends back null. Im 99% sure the PasswordResetToken that im trying to fetch is inside of the repository because i did some system.out.println's and it shows the PasswordResetToken is in there with the matching token it just doesnt send anything back.
heres the email varification and token creation
#PostMapping("/resetPassword")
public String resetPassword(#RequestBody PasswordModel passwordModel, HttpServletRequest request) {
User user = userService.findUserByEmail(passwordModel.getEmail());
String url = "";
if(user!=null) {
String token = UUID.randomUUID().toString();
userService.createPasswordResetTokenForUser(user, token);
url = passwordResetTokenMail(user, applicationUrl(request), token);
}
return url;
}
service methods that go along with it
#Override
public User findUserByEmail(String email) {
return userRepository.findByEmail(email);
}
#Override
public void createPasswordResetTokenForUser(User user, String token) {
PasswordResetToken passwordResetToken
= new PasswordResetToken(user, token);
passwordResetTokenRepo.save(passwordResetToken);
}
save new password functionality where the issue happens
#PostMapping("/savePassword")
public String savePassword(#RequestParam("token") String token,
#RequestBody PasswordModel passwordModel) {
String result = userService.validatePasswordResetToken(token);
if(!result.equalsIgnoreCase("valid")) {
return "Invalid token";
}
Optional<User> userOptional = userService.getUserByPasswordResetToken(token);
if(userOptional.isPresent()) {
userService.changePassword(userOptional.get(), passwordModel.getNewPassword());
return "Password has been updated";
} else {
return "Invalid token";
}
}
service methods
#Override
public String validatePasswordResetToken(String token) {
PasswordResetToken passwordResetToken
= passwordResetTokenRepo.findByToken(token);
if(passwordResetToken == null) {
return "invalid";
}
User user = passwordResetToken.getUser();
Calendar cal = Calendar.getInstance();
if((passwordResetToken.getExpirationTime().getTime())
- cal.getTime().getTime() <=0) {
passwordResetTokenRepo.delete(passwordResetToken);
return "expired";
}
return "valid";
}
#Override
public Optional<User> getUserByPasswordResetToken(String token) {
return Optional.ofNullable(passwordResetTokenRepo.findByToken(token).getUser());
}
#Override
public void changePassword(User user, String newPassword) {
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
and finally the repository
#Repository
public interface PasswordResetTokenRepo extends JpaRepository<PasswordResetToken, Long> {
PasswordResetToken findByToken(String token);
}
Figured it out incase anyone runs into the same issue. I was using postman on the client side and for some reason when I was copying the URL and pasting it into postman it was adding a space after the url so the token ended up being token=xyz with a space after instead of just token xyz. That’s why it looked like the token was there in the repo because you couldn’t tell there was a space after it.

REST to return content after POST on Java service

New users are created with the POST method, and after this the database will attach it an unique ID, which is necessary to create a token.
The only way to create a token, is after the process of user creation. I query it from the database and now it should have ID on it, but I can't figure how to do that.
Is there any way to retrieve the entity from database right after creating it?
If looked at this similar question, but couldn't find an answer: Is it ok by REST to return content after POST?
#POST
#Consumes({MediaType.APPLICATION_JSON})
public Response create(#QueryParam("email") String email, #QueryParam("username") String userName, #QueryParam("password") String password) {
if (TextUtil.isEmpty(userName) || TextUtil.isEmpty(password) || TextUtil.isEmpty(email)) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
User newUser = new User();
newUser.setEmail(email);
newUser.setUsername(userName);
newUser.setPass(password);
super.create(newUser); //ths line create user in database
String id = newUser.getUid() + ""; //the newUser has no id yet so it is null but I want this id from database
return Response.status(Response.Status.OK).entity("id: " + id).build();
}
}
Create super.method
public void create(T entity) {
getEntityManager().persist(entity);
}
I generate REST api using this tutorial
https://netbeans.org/kb/docs/websvc/rest.html
getEntityManager().persist(entity);
Set the id of the entity by calling entity.setId(...).
I my comments I show how to sync EntityManager. Maybe you should write a better create method:
public <T> T create(T t) throws Exception {
try {
entityManager.persist(t);
entityManager.flush();
} catch (Exception e) {
throw e;
} finally {
entityManager.clear();
}
return t;
}
And then
newUser = super.create(newUser);

Define custom error message in REST using Spring on Weblogic

I am hosting Spring Boot app on weblogic 10.3.6 for REST api
I'd like to implement these 2 features:
Whenever my custom exception occurs I would like to send a http response with message e.g. 500 - "request couldn't be parsed because of something..."
Whenever any error is thrown I would like to get the stack trace that is usually printed to console (for debugging purposes)
I tried to solve the first part the following way:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(MyException.class)
public ResponseEntity handleException(MyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
However as a response I only get 500 Internal Server Error with no message
As for the second part I tried simmilar approach but there was no error message either.
How can I solve this?
EDIT:
The best I could achieve was removing ExceptionHandlerAdvice and using annotation on my exception class instead like this:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason="This is a message I'd like to send")
It works, but I have to add some variables from code to the message and this method does not allow it
EDIT2:
This is a bit weird, perhaps a behavior of weblogic server, but when I set the httpStatus to HttpStatus.ACCEPTED I can see the message, if it is HttpStatus.Forbidden or any other 4xx error I just get the error without message
Create 'ResponseEntity' object with message and status and return it, it will display with error message.
/**
* HTTP_STATUS - 500 -Service Unavailable.
*
* #param exception
* Catches the following: MyException
* #return
*/
#ExceptionHandler({ MyException.class})
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public static ResponseEntity<?> handleConnectionErrorResponse(MyException exception) {
return new ResponseEntity<String>("Some error occurred at server", HttpStatus.INTERNAL_SERVER_ERROR);
}
Do not return something, throw an :
#ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
public class UnprocessableEntity extends RuntimeException {
public UnprocessableEntity(String string) {
super(string);
}
}
Or another thing like that.
I also went through the same requirement.
Below is the code which is working for me:
String errMsg = "{\"errorMessage\":\"Parking Slot is not available\"}";
return new ResponseEntity<String>(errMsg, HttpStatus.INTERNAL_SERVER_ERROR);
Whereas errMsg should be written in the format which you want. Like I had requirment for response in JSON.
Hope this will help some of you
Well In my case I have done custom error handling logic.
We can define a custom Base Response class wich accepts generic type(Eg: user desired model)
Return BaseResponse as a response for each REST Methods
(GET, PUT, POST, DELETE, etc)
BaseResponse.java
public class BaseResponse<T> {
int statusCode;
String message;
T data;
public int getStatusCode() {
return statusCode;
}
public BaseResponse<T> getValidResponse(String message, T data) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = 200;
baseResponse.message = message;
baseResponse.data = data;
return baseResponse;
}
public BaseResponse<T> getErrorResponse(int StatusCode, String message) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = StatusCode;
baseResponse.message = message;
return baseResponse;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
User.java
public class User {
String userName;
String userAddress;
String userEmail;
String userPhoneNumber;
...
//Getters & Setters
}
YourController.java
....
#PostMapping("/addUser")
public BaseResponse<User> addUser(User user) {
if (user.getUserName() != null && !user.getUserName().equals("")) {
UserEntity userEntity = new UserEntity();
userEntity.setName(user.getUserName());
...
userRepository.save(userEntity);
return new BaseResponse<User>().getValidResponse("Successfully Added User", user);
} else {
return new BaseResponse<User>().getErrorResponse(400, "Name field is required");
}
}
...
#DeleteMapping("/deleteUser/{userId}")
//Using ? for uncertain Response.Eg: Some response might have data response and some may not have data response...
public BaseResponse<?> deleteUser(#PathVariable(value = "userId") int userId) {
//After delete operation...we don't require data response.
return new BaseResponse<User>().getValidResponse("Successfully deleted the User", null);
}
This might not be an exact solution for the question asked but will surely help someone else.

Custom http code with Spring MVC

I use the following code to handle rest calls using Spring MVC.
#RequestMapping(value = "login", method = RequestMethod.GET)
public #ResponseBody
User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
...
return user;
}
I would like to send the client customer http codes for wrong username, wrong passwords, password changed and password expire conditions. How can I modify the existing code to send these error codes to the client?
You can use controller advice to map exception thrown within controller to some client specific data at runtime.
For example if user is not found, your controller should throw some exception (custom or existed one)
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UserNotFoundException(username); //or another exception, it's up to you
return user;
}
}
Then you should add #ControllerAdvice that will catch controller exceptions and make 'exception-to-status' mapping (pros: you will have single point of responsibility for 'exception-to-status-mapping'):
#ControllerAdvice
public class SomeExceptionResolver {
#ExceptionHandler(Exception.class)
public void resolveAndWriteException(Exception exception, HttpServletResponse response) throws IOException {
int status = ...; //you should resolve status here
response.setStatus(status); //provide resolved status to response
//set additional response properties like 'content-type', 'character encoding' etc.
//write additional error message (if needed) to response body
//for example IOUtils.write("some error message", response.getOutputStream());
}
}
Hope this helps.
One way is to add some additional classes for returning HTTP error. Your code will looks like this:
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UnauthorizedException();
return user;
}
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException{
}
In this case user will get 401 response status code
I hope it helps
You can return an HTTP 500 or code of your choosing (from the org.springframework.http.HttpStatus enumeration) and use a custom error to emulate something like a SOAP fault within the JSON response.
For example:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(YourTargetException.class)
#ResponseBody
Fault caughtYourTargetException(HttpServletRequest req, Exception ex) {
String code = ex.getClass().getName();
String reason = "Caught YourTargetException."
return new Fault(code, reason);
}
The Fault class could look something like this (inspired by http://www.w3.org/TR/soap12-part1/#soapfault):
/**
* A Fault is an object that can be serialized as JSON when expected errors occur.
*/
public class Fault {
#JsonProperty("faultCode")
private final String code;
#JsonProperty("faultReason")
private final String reason;
#JsonProperty("faultDetails")
private final List<String> details = new ArrayList<>();
public Fault(String code, String reason) {
this.code = code;
this.reason = reason;
}
public Fault(String code, String reason, String... detailEntries) {
this.code = code;
this.reason = reason;
details.addAll(Arrays.asList(detailEntries));
}
public String getCode() {
return code;
}
public String getReason() {
return reason;
}
/**
* Zero or more details may be associated with the fault. It carries
* additional information relative to the fault. For example, the Detail
* element information item might contain information about a message
* not containing the proper credentials, a timeout, etc.
* #return Zero or more detail entries.
*/
public Iterable<String> getDetails() {
return details;
}
#Override
public String toString() {
return String.format("Fault %s occurred. The reason is %s.", getCode(),
getReason());
}
}
You could use one of the existing SOAPFaults in Java frameworks, but I have found they don't play well in REST. Creating my own simple version turned out to be simpler.
You can define your own status code and returning objects. In your code throw custom exceptions and then define an exception handler as follows:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyRetObject> handleControllerError(HttpServletRequest req, MyException ex) {
LOG.warn("My error", ex);
MyRetObject errorMessage = new MyRetObject(ex.getMessage());
return ResponseEntity.status(600).body(errorMessage);
}
}
In your case replace MyExeption.class by UserNotFoundException.class and build your customer error response object and error code

HtmlUnit's WebClient fails on second identical loadWebResponse() call

I'm attempting to write tests for a very long and kludgy "getPost" method in a webapp I'm working on for my job. I'm using JUnit, HtmlUnit, and Jetty's ServletTester to approximate sending a request to a servlet and receiving a response. I've managed to get it mostly working, but I'm having a problem. I'm trying to test the login functionality. If the user logs in successfully, the server should send some JSON back to the client with the user's information. If the user is already logged in, the server should send back "result": "failure" and an error message.
My problem comes when I try to test the second requirement. I can log in successfully, and get the correct data back. However, when I try to send the request again, it returns 404: not found. I tried breaking the code up into different tests, but I have to be able to call login twice in order to test the second requirement. Later tests in the JUnit file run just fine, and the servlet is staying connected the same time. I tried making a second, identical request, but that also failed. I've searched the internet to no avail. In short, I'm stumped.
Here's what I'm working with (unnecessary code has been edited out):
//In MyFunServlet class:
private final static String USERID_ATTRIBUTENAME = "userid";
void doPost(HttpServletRequest request, HttpServletResponse response) {
String action = request.getParameter("opt");
final HttpSession session = request.getSession();
if(action != null){
Long userId = (Long)session.getAttribute(USERID_ATTRIBUTENAME);
if(userId != null){
//do stuffz
} else {
if(action.equals("login")) {
User user = LoginUser(request, response);
try{
JSONObject json = new JSONObject();
if(request.getAttribute("result") == "success"){
json.put("result", "success");
json.put("id", user.getId());
json.put("name", user.getName());
} else {
json.put("result", "failure");
json.put("message", request.getAttribute("message"));
}
SendJSONResponse(json, request, response);
}catch(Exception e){
}
} else {
System.out.print("Unknown opt: " + action);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}
}
private void LoginUser(HttpServletRequest request, HttpServletResponse response){
final HttpSession session = request.getSession();
User user = null;
Long userId = (Long)session.getAttribute(USERID_ATTRIBUTENAME);
if(userId != null){
request.setAttribute("result", "failure");
request.setAttribute("message", "The user is already logged in.");
} else {
final String email = request.getParameter("accountEmail");
final String password = request.getParameter("accountPassword");
if(email != null) {
user = helperClass.magicallyGetUserByEmail(email);
if(user != null){
if(user.getPassword().equals(password)){
session.setAttribute(USERID_ATTRIBUTENAME, user.getId();
request.setAttribute("result", "success");
}
}
} else {
request.setAttribute("result", "failure");
}
}
return user;
}
private void SendJSONResponse(JSONObject json, HttpServletRequest request,
HttpServletResponse response) {
String contentStr = json.toString();
response.setContentType("application/json");
response.setStatus( HttpServletResponse.SC_OK);
response.setContentLength(contentStr.length());
response.getWriter().print(contentStr);
response.flushBuffer();
}
For reference purposes, this file is 1084 lines long. The doPost method is about 900 of those. Disclaimer: this is not my code. I did not write it. I only have to test it.
Now for the test:
//In MyFunServletTest.java:
//using JUnit 4
public class MyFunServletTest {
static ServletTester tester;
static String baseUrl;
WebClient webClient = new WebClient();
User user;
WebRequest loginRequest;
#BeforeClass
public static void initClass(){
tester = new ServletTester;
tester.setContextPath("/");
tester.addServlet(MyFunServlet.class, "/fun.service");
baseUrl = tester.createSocketConnector(true);
tester.start();
}
#AfterClass
public static void cleanClass() {
tester.stop();
}
#Before
public void preTest(){
//taking values from our magical test user
user = new User();
user.setEmail("blah#blah.com");
user.setPassword("secure");
loginRequest = new WebRequest(baseUrl + "/fun.service", HttpMethod.POST);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new NameValuePair("opt","login"));
params.add(new NameValuePair("accountEmail", user.getEmail());
params.add(new NameValuePair("accountPassword", user.getPassword());
loginRequest.setRequestParameters(params);
}
#Test
public void testLogin() {
WebResponse response = webClient.loadWebResponse(loginRequest);
JSONObject responseJSON = new JSONObject(response.getContentAsString());
//this test passes
assertEquals("success", responseJSON.getString("result"));
response = webClient.loadWebResponse(loginRequest);
//this test fails
assertTrue(404 != response.getStatusCode());
//this then causes an error, as response.getContentAsString() is null.
esponseJSON = new JSONObject(response.getContentAsString());
}
}
Help? Where am I missing something?
Thanks.
Without the ability to run the test myself, I can only offer some approaches:
Try creating two JSONObject objects to store the two responses separately, and compare the two (either print them or using the debugger), see if anything looks odd there.
If that doesn't tell you anything, create two separate identical request instances and use each.
Then try tracing through the call to loadWebResponse to see exactly what URL is being requested (cranking up the log level might tell you this, too).
If the 404 is correct, then the second request is somehow being mangled, but the question would be WHERE.

Categories