SpringMVC ErrorHandling with JavaScript UI - javascript

I have a SpringMVC 3.2 service with a HTML/endoUI UI that I am unable to return messages to. It would seem that the RequestAttributes that I populate are not visible to the Javascript. All the examples I have seen for SpringMVC error handling use either html or jsp redirects.
Here is a sample:
#Controller
#RequestMapping( "/officeSignUp" )
public class OfficeSignUpController
..
#RequestMapping( value = "/stepOne", method = RequestMethod.POST )
#ResponseBody
public String officeCreationStepOne( #Valid #ModelAttribute( "officeSetup" ) OfficeSetup officeSetup,
BindingResult result, Model model, RedirectAttributes attributes ) {
String results;
results = newOfficeValidator.validateStepOne( officeSetup, result );
NewCustomerSignup newCustSignup = newOfficeValidator.validateNewOwner( officeSetup );
model.addAttribute( newCustomerSignupRepository.save( newCustSignup ) );
return results;
}
My validator is:
#Component
public class NewOfficeValidator {
public String validateStepOne( OfficeSetup officeSetup, BindingResult result ) {
List<String> errors = new ArrayList<String>();
if (result.hasErrors()) {
for (ObjectError error : result.getAllErrors()) {
errors.add( error.getDefaultMessage() );
}
errors.add( "redirect: " + VIEW_STEP_ONE );
// RuntimeException with a list of strings in it
throw new OfficeSetupException( errors, "Validation in StepOne" );
}
return VIEW_STEP_TWO;
}
And in my BaseController I catch the exception, retrieve the error messages and use them:
#ExceptionHandler
#ResponseStatus( HttpStatus.BAD_REQUEST )
#ResponseBody
public ErrorMessage handleOfficeSetupException( OfficeSetupException ex ) {
List<String> errors = ex.getErrors();
StringBuilder sb = new StringBuilder();
for (String error : errors) {
sb.append( error );
}
// this is just a bean with a list of Strings
return new ErrorMessage( errors );
}
When an exception is thrown, instead of a json or string response, I get a tomcat html message which contains:
<h1>HTTP Status 400 - Received OfficeSetupException </h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Received OfficeSetupException </u></p><p><b>description</b> <u>The request sent by the client was syntactically incorrect (Received OfficeSetupException ).</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.27</h3></body></html>
and then in the javascript :
if(stepString == "Step 2"){ click on step 2
if(viewModelStepOne.validateStepOne(s) == true){ client side validation
viewModelStepOne.saveStepOne(s);if above validation passes, send request to server,
if(serverValidationFailed){if server returns error, do not move to step 2
s.preventDefault();
}else{ move to step
this.disable(this.tabGroup.children("li").eq(2),true);
this.enable(this.tabGroup.children("li").eq(3),true);
}
}else{ s.preventDefault();
}
}
So ultimately my question is this: what is the best way to return validation or other error messages from spring mvc to a javascript front end? I am trying to use JSON for my responses, so I would expect the ErrorMessage to be returned as such.

In my projects I do as you described - throw an exception and catch it with the #ExceptionHandler. But you don't need to return just a string message. Create your own exception class that can take Map or List of errors as arguments (or whatever else you want to use as an errors model). When an error happens populate your custom execption (let's call it RequestException) with the errors and then throw it. Errors will be stored in your exeception instance. In #ExceptionHandler for your RequestException fetch the errors from the exception object and return then to frontend in a way convenient to you.

I think I solved it by adding the "#JsonAutoDetect" to my Exception. I am using Java Config so the following appears to handle the HTTP response code and return a JSON response.
#JsonAutoDetect( fieldVisibility = Visibility.DEFAULT, getterVisibility = Visibility.DEFAULT, isGetterVisibility = Visibility.DEFAULT )
public class OfficeSetupException extends RuntimeException {
private List<String> errors;
private static final long serialVersionUID = -1L;
public OfficeSetupException( List<String> errors, String message ) {
super( message );
this.errors = errors;
}
public List<String> getErrors() {
return errors;
}
}

Related

How to sent JavaScript Objects from the Client-Side and how to receive and parse them in Spring Boot Back-end?

I have came and left this problem numerous times while trying to make my web apps and have gotten fed up with no results, to the point that I have to ask here, so please excuse me if I come off as venting... I am quite aggravated.
I am trying to send data in the form of key-value pairs from my client(vanilla js) to my back end(spring boot java). I have tried numerous ways of doing it but can't seem to find the correct way/combination to achieve what I want done. My current non-working code is as follows.
Client-Side JS
var object = {
'id' : 1,
'username' : 'jumpthruhoops',
'password' : 'melodysteez'
};
Axios
.post('http://localhost:8080/gameSchedule', JSON.stringify(object))
.then((response) => {
console.log(response.data);
});
Back-End Spring Boot/Java
#CrossOrigin
#RequestMapping(value = "/gameSchedule", headers = "Accept=application/json", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String getSchedule(#RequestBody String user) {
System.out.println(user);
return "test";
}
The following code is what I currently have that has given me any type of results close to what I'm looking for. It gives me the following printed line...
%7B%22id%22%3A1%2C%22username%22%3A%22tdellard1%22%2C%22password%22%3A%22sisters3%22%7D=
...which I believe is a hex code for the string object I passed into the parameter. I'm not sure if this is from Spring Boot, or if this is what JSON.stringify does. Since the User Object is a test object and actual object that I plan on passing in, is way more complex, I don't want to figure out how to decode the hex code, unless I can't get anything else going and I completely have to.
Because it is more complicated, I don't want to use a lot of #RequestParams("name") String VaribleName like 40 times in the parameter of the method. This was also the only other way to get results but passing those variables into a url is maddening.
Some other things I have tried are #ModelAttribute and (#RequestBody User user), both return errors, one that seems to be reoccurring is
018-10-30 23:38:29.346 WARN 12688 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]
So what I am pretty much asking is for guidance on what is the best way to send my data from Axios(form.serialize, JSON.stringify, JavaScript Object, etc.) and what corresponding method I need to use to obtain that data on my Spring Boot Back-End and make it manipulative so I can turn it into a POJO.
Just remove JSON.stringify(object) and put object.
Axios
.post('http://localhost:8080/gameSchedule', object)
.then((response) => {
console.log(response.data);
});
You can see an example on POST request here axios documentation
On Spring boot you have to create an entity like this:
#Entity
public class UserAccount implements Serializable {
#Id
private Long id;
#Column(unique = true, nullable = false)
#Size(max = 255)
private String userName;
#Column(nullable = false)
#NotNull
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
and change your code for here
#CrossOrigin
#RequestMapping(value = "/gameSchedule", headers = "Accept=application/json", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public UserAccount getSchedule(#RequestBody UserAccount user) {
System.out.println(user.getUserName());
return user;
}
If you are sending an object you have to use object when receiving at back-end side and make sure that name of the field in request object and the field name of the class at back-end side must be same,
so it should be like this:
I am just making some changing in your code to access field:
var data = {
'id' : 1,
'username' : 'jumpthruhoops',
'password' : 'melodysteez'
};
// name of variable(data variable) doesn't matter but inside everything consider as a body
axios.post('http://localhost:8080/gameSchedule', JSON.stringify(object), {
headers: {
'Content-Type': 'application/json',
}
}
);
back-end side retrieve fields
//create one Student class to map fields with body
#CrossOrigin
#RequestMapping(value = "/gameSchedule", headers = "Accept=application/json", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String getSchedule(#RequestBody Student student) {
System.out.println(student.id);
System.out.println(student.username);
System.out.println(student.password);
return "test"
}

What is the correct url to access an file via AJAX?

To contextualize:
There is a remote directory, clearing-dit\logs, which has a series of logs (portal.log, test.log, ...). This directory is mapped to an HTML page, where all your .log's are displayed. Once one of them is clicked, its respective content is displayed.
Exemple
Currently, I'm using thymeleaf to show the content:
<html xmlns:th="http://www.thymeleaf.org" th:include="layout :: page">
...
<div ...>
<p th: utext = "$ {log.content}"> Log content </ p>
</div>
The problem is that this content is displayed in a static way, and I need it to be continue shown as the file is getting updates. I went searching and saw that I can/need to do it through an AJAX, but the concept of AJAX is quite vague to me.
Currently, I'm trying to do it in a very simple way:
$.ajax({
url : "/log",
type : "post",
success : function(data) {
document.getElementById('content').innerHTML = data;
}
});
And (to set the log content):
#RequestMapping(value = "/log", method = RequestMethod.POST)
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
if (log.getInitLine() == 0 && log.getFinalLine() == 0) {
try {
fileNumberLines(log);
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
} catch (IOException e) {
logger.error(e.getMessage());
}
} else {
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
}
model.put("path", logsDir);
model.put("log", log);
model.put("currentPage", "logs");
model.put("root", root);
return "log";
}
But instead of the contents of the file, I'm getting the page itself.
Return AJAX call
What makes sense, since I'm passing the url of the page itself. So, my question is: How do I access log content through the url? What is the correct url?
You should not return the name of the view from the Controller method if you are using it for AJAX. You need to return data (an Object) from the method. You will need the data to be converted to JSON (which can be used in Javascript) so you need to mark the method with #ResponseBody. You will also need jackson-databind on the classpath (com.fasterxml.jackson.core jackson-databind) for the JSON conversion.
#RequestMapping(value = "/log", method = RequestMethod.POST)
#ResponseBody
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
if (log.getInitLine() == 0 && log.getFinalLine() == 0) {
try {
fileNumberLines(log);
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
} catch (IOException e) {
logger.error(e.getMessage());
}
} else {
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
}
model.put("path", logsDir);
model.put("log", log);
model.put("currentPage", "logs");
model.put("root", root);
return log;//return an Object containing data (or your own Value Object)
}
You're getting the file itself, because you're returning a view name from the method. So, Spring doesn't know if you're asking it to return only the data. One way to get this done is to use #ResponseBody annotation.
This allows you to send any arbitrary data to the client. So, you may want to change your method to something like this:
#RequestMapping(value = "/log", method = RequestMethod.POST)
#ResponseBody
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
// Code truncated for brevity
return log;
}
What we're doing here is instructing Spring to return the contents of log object directly to the client, instead of rendering a view. Hope this helps.

AngularJS 400 Bad request

I'm trying to implement some post functionality in my app.
I have got the following post method:
restrictLoginAttemptsFromSingleIp: function (id, userId) {
var serviceUri = baseServicesUrlService.getBaseServicesUrl() + "/employee-service/restrict-single-ip";
return $http.post(serviceUri, {restrictLoginAttemptIp: {loginAttemptIds: [id]}, dataOwnerId: userId});
}
My server side is using RESTEasy 3.0.4 with Hibernate validation:
#POST
#Path("/restrict-single-ip")
public Response RestrictSingleIp(#Valid RestrictLoginAttemptIpRequest requestData, #Context HttpRequest request){
return Response.status(200).build();
}
The RestrictLoginAttemptIpRequest class inherits one field (dataOwnerId) of type Long from PostBase:
public class RestrictLoginAttemptIpRequest extends PostBase {
private RestrictLoginAttemptIp restrictLoginAttemptIp;
public RestrictLoginAttemptIp getRestrictLoginAttemptIp() {
return restrictLoginAttemptIp;
}
public void setRestrictLoginAttemptIp(RestrictLoginAttemptIp restrictLoginAttemptIp) {
this.restrictLoginAttemptIp = restrictLoginAttemptIp;
}
}
The RestrictLoginAttemptIp class:
package blah;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.List;
public class RestrictLoginAttemptIp {
#NotEmpty(message = "blah")
private List<Long> loginAttemptIds;
public List<Long> getLoginAttemptIds() {
return loginAttemptIds;
}
public void setLoginAttemptIds(List<Long> loginAttemptIds) {
this.loginAttemptIds = loginAttemptIds;
}
}
I get the following data string from the POST request which seems to be ok:
{restrictLoginAttemptIp={loginAttemptIds=[328]}, dataOwnerId=8}
Can someone please explain me why I get an 400 Bad request error when I invoke that function?
Is this because of Long datatypes? Should I somehow mark them in Javascript to be Longs?
Ok after 4 hours I figured out the problem.
The case is, that I'm reading the POST data (solving permission questions) in a security interceptor. Reading POST data in RESTEasy is a little bit tricky. To create a LinkedHashMap I use Apache IOUtils (https://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/IOUtils.html) like it is figured out in the next code snippet
String result = IOUtils.toString(requestContext.getEntityStream());
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(result, Object.class);
I looked up in my AngularJS interceptor (used for example for putting something in the header of every request) and figured out, that the server cannot read the input stream: java.io.ioexception no content to map to object due to end of input.
At the end the problem was, that after I once read the EntityStream of the ContainerRequestContext it became empty. The solution was to repopulate it after reading POST data. Something like this:
private LinkedHashMap getPostData(ContainerRequestContext requestContext) {
Object obj = null;
try {
String result = IOUtils.toString(requestContext.getEntityStream());
ObjectMapper mapper = new ObjectMapper();
obj = mapper.readValue(result, Object.class);
//IMPORTANT: After you can get the entity stream only once. After reading the entity stream is empty
//so the JSON parser cannot convert EMPTY entity stream into any object. To avoid strange errors (like 400 Bad Request)
//you have to convert the string back to input stream and rewrite the empty entity stream.
InputStream stream = IOUtils.toInputStream(result);
requestContext.setEntityStream(stream);
System.out.println(obj);
} catch (IOException e) {
e.printStackTrace();
}
return (LinkedHashMap) obj;
}
P. S. ObjectMapper comes from Jackson

jQuery Ajax request with error 404 (not found) but it works

I'm sending a jQuery AJAX request to the server, but the browser's console tells me that the page is not found. Still, my Spring MVC signature mapping the requested URL is executed, and yet the done part of the AJAX function is not.
Here is the relevant code:
Javascript:
var content = $(data).filter("#wrapper-email-content");
$.ajax({
url : '/sendEmail',
type : 'POST',
data : {
content: content.html()
}
}).done(function(){
console.log("Finished")
});
Spring MVC Signature:
#RequestMapping(value = "/sendEmail", method = RequestMethod.POST)
public void sendEmail(
HttpServletRequest request, String content) {
UserTO user = (UserTO) request.getSession().getAttribute("USER");
String email = user.getEmail();
String title = "Your order";
Email.sendEmail(title, email, content, null, null, null);
}
So, in the code, the email is sent, meaning that sendEmail method is executed, but I still get the Error 404: not found from the AJAX request, and "Finished" is not printed in the console.
What am I doing wrong?
#RequestMapping(value = "/sendEmail",headers="Accept=application/json", method = RequestMethod.POST)
public Map<String,String> sendEmail(
HttpServletRequest request, #RequestBody String content) {
Map<String,String> statusMap=new HashMap<String,String>();
UserTO user = (UserTO) request.getSession().getAttribute("USER");
String email = user.getEmail();
String title = "Your order";
Email.sendEmail(title, email, content, null, null, null);
statusMap.put("status","Mail sent successfully");
return statusMap;
}
The better solution is annotating your method with
#ResponseStatus(value = HttpStatus.OK)
see answer https://stackoverflow.com/a/12839817/954602 for the example.
EDIT: see solution posted for What to return if Spring MVC controller method doesn't return value?
Not familiar with MVC Spring, but you probably need to return a success status code yourself:
public String sendEmail(
HttpServletRequest request, #RequestBody String content) {
UserTO user = (UserTO) request.getSession().getAttribute("USER");
String email = user.getEmail();
String title = "Your order";
Email.sendEmail(title, email, content, null, null, null);
return new ResponseEntity<String>(json,HttpStatus.OK);
}

angular promise returning __proto__

I have an asp.net-mvc app using angular. I had a get method that returns some data from the server. What I was returning was a Tuple with a status message and a second piece of data that was either an error message or the actual data. Ex:
return Json(new Tuple<string, string>("error", "bad request"));
//or
return Json(new Tuple<string, MyData>("success", new MyData());
this was working fine, in angular I did the following:
$http.get(url).then(
function (result) {
return result.data;
},
function (result) {
$q.reject(result.data);
}
here, result.data.Item1 was the first item of my Tuple
However, I changed my return types from Tuple to a new custom type that I created that looks like the following:
public class ServerResponse <T1, T2, T3>
{
T1 status { get; set; }
T2 message { get; set; }
T3 data { get; set; }
public ServerResponse(T1 status, T2 message, T3 data)
{
this.status = status;
this.message = message;
this.data = data;
}
}
but now when I do:
result.data.status
I dont get expected results because result.data is returning something like this:
__proto__ : Object
and I'm not sure how to fix this.
after looking around and comparing to the Tuple class, I noticed that my ServerResponse class's properties were not marked public so they weren't being found. After making them public the problem went away

Categories

Resources