Ignore exceptions while converting a #RequestParam value - spring

I have an endpoint that takes a request parameter: http://localhost/test?parameter=123
When somebody calls this endpoint with a string instead of an integer he gets a BAD_REQUEST response because the string can not be converted.
Is it possible to ignore conversion exceptions on a request parameter and just leave it empty?`
Currently my code looks like this:
#RequestMapping(value = "/test")
public void doSomething(#RequestParam(required = false) Integer parameter) {...}

You should take the parameter as String and convert yourself.
By saying it should be Integer in your method signature you require this indeed to be an integer. If it was not it is indeed BAD_REQUEST. If you want other custom scenario you should implement it yourself.
#RequestMapping(value = "/test")
public void doSomething(#RequestParam(required = false) String parameter) {
Integer parameterValue = null;
if (parameter != null) {
try {
parameterValue = Integer.valueOf(parameter);
} catch (NumberFormatException ex) {
// No-op as already null
}
}
// At this point the parameterValue is either null if not specified or specified as non-int, or has the integer value in it
}

Related

Why byte array becomes string while transferring it via rest template in Java

These are two SpringBoot projects. A is for api service providing, and B is consuming A's service via rest template. It is OK while transferring string. While transferring Excel file via byte array, B receives a string, not byte array.
The api method in A.
#GetMapping(value = "/export")
#ResponseBody
public HSResult exportFile(#RequestParam(value = "fileName", defaultValue = "-1") String fileName,
#RequestParam(value = "provider", defaultValue = "-1") String channelId,
#RequestParam(value = "fileStatus", defaultValue = "-5") int fileStatus,
#RequestParam(value = "cdate", defaultValue = "") String cdate,
HttpServletRequest request, HttpServletResponse response) {
// ...... some logic code
InputStream inputStream=new FileInputStream(fullPath);
long fileSize=new File(fullPath).length();
byte[] allBytes = new byte[(int) fileSize];
inputStream.read(allBytes);
result = HSResult.build(200, "exportFile success",allBytes);
return result;
}
The consuming method in B.
public ResponseEntity downLoadFile(int businessType, String fileName, int fileStatus, HttpServletResponse response) {
//.......
ResponseEntity<HSResult> responseEntity = restTemplate.getForEntity(url, HSResult.class);
HSResult apiResult = responseEntity.getBody();
byte[] fileData = (byte[]) apiResult.getData();
//.......
}
A reads an excel file from disk into a byte array before transferring.
But while receving the result in B side, it is string like UEsDBC0AAAAIADhMuVAKVjbC//////////8LABQAX3Jl
Why did this happen? And how to transfer byte array through rest template correctly? Thanks.
PS: The class of HSResult
public class HSResult {
private Integer status;
private String msg;
private Object data;
//gets and setters
//......
}
Finally, I find the root cause. Share it with who may encounter the same issue.
In A side, it puts a byte array in the data field of HSResult, this field is in type object.
While receving this in B side, rest template is trying to cast all the data back into HSResult. When it comes to the byte array, the receving field data is in type object. So rest template does not konw what the specific type it is, then convert the byte array into a string, with some decode. I don't know whether it is exactly UTF8 or GB2312 or else.
How to resolve this? Just specify the receving field with specific type, not object.
public class HSResult {
private Integer status;
private String msg;
private byte[] data;
//gets and setters
}
Then everythiing is OK.
Thanks for Ken's reminder. Http is a text protocal, it cannot transfer byte array directly, so byte array must be converted into string at first.
I have used the Gson() class to convert object to json! and it has no problem with byte arrays.
I have this problem when I want to move my codes to spring, so I solved it by serializing byte[] filed:
class ByteArraySerializer: JsonSerializer<ByteArray>() {
override fun serialize(bytes: ByteArray, jsonGenerator: JsonGenerator, serializerProvider: SerializerProvider?) {
val intArray = intArray(bytes)
jsonGenerator.writeArray(intArray, 0, intArray.size)
}
private fun intArray(input: ByteArray): IntArray {
val ret = IntArray(input.size)
for (i in input.indices) ret[i] = input[i].toInt() // & 0xff -> 0-255
return ret
}
}
convert byte array to int array
and then use it in my class:
data class VideoInfo(val durationMS: Int = 0): Serializable {
#JsonSerialize(using = ByteArraySerializer::class)
var bytes: ByteArray? = null
}
It will return json object as a below:
{
"bytes": [-1,-40, ..., 0],
"durationMS": 8870
}
It is kotlin, You can easily convert it to java :)

AssertTrue validation does not give right error message in jhi-alert-error

I have added a validation in my DTO like this:
#AssertTrue(message = "validation.amountNot0RateAbove1" )
public boolean isAmountNot0RateAbove1() {
return amount == 0 ? true : rate != null && rate > 1;
}
this gives an error like this:
Resolved exception caused by Handler execution: org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<nl.tibi.sbys.service.dto.ProjectDTO> nl.tibi.sbys.web.rest.client.ClientProjectResource.updateProject(nl.tibi.sbys.service.dto.ProjectDTO) throws java.net.URISyntaxException, with 1 error(s): [Field error in object 'projectDTO' on field 'position[0].amountNot0RateAbove1': rejected value [false]; codes [AssertTrue.projectDTO.position[0].amountNot0RateAbove1,AssertTrue.projectDTO.position.amountNot0RateAbove1,AssertTrue.position[0].amountNot0RateAbove1,AssertTrue.position.amountNot0RateAbove1,AssertTrue.amountNot0RateAbove1,AssertTrue.boolean,AssertTrue]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [projectDTO.position[0].amountNot0RateAbove1,position[0].amountNot0RateAbove1]; arguments []; default message [position[0].amountNot0RateAbove1]]; default message [validation.amountNot0RateAbove1]]
the response is (type httpErrorResponse):
error:
fieldErrors:
Array(1)0:
field:"position[0].amountNot0RateAbove1"
message:"AssertTrue"
objectName:"projectDTO"
jhi-alert-error shows this message:
translation-not-found[error.AssertTrue]
what i would like to show is a custom error message so with amountNot0RateAbove1 key.
somehow the errors from the java code are not send to the front-end only the last key AssertTrue is send.
how should a change it?
i think it would be best if the first error from the java code would be send as message and not the last. so AssertTrue.projectDTO.position[0].amountNot0RateAbove1 instead or even better take the default message if set which is the message from the annotation:
#AssertTrue(message = "validation.amountNot0RateAbove1" )
any help?
as a not so nice workaround i tried to edit the respons setting the message in the line of this:
private onSaveError(res: HttpErrorResponse) {
console.log('res:', res);
res.error.fieldErrors[0].message = 'validation.amountNot0RateAbove1';
console.log('res:', res);
this.isSaving = false;
}
but although the code is executed it did not change the message.
this, src/main/webapp/app/shared/alert/alert-error.component.ts component makes the error message. i could do something there unless someone has a better solution :)
i think ExceptionTranslator is the place to be for improving the returned error message.
===========================edit==========================
here it can be changed. not sure what the effects will be for other errors:
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
.collect(Collectors.toList());
the f.getCode() will return the last code in the list which is the most general one. the first is the most specific.
seconde the message set in the AssertTrue annotation is found in the f.getDefaultMessage()
not sure if i should go for the first code or the default message
I still hope someone has a better solution but this is what i did. This code will keep the default way of working which is getting the most general message from the fielderror with getCode. But in rare cases you can overwrite it in this way:
ExceptionTranslator
public static final String USE_MESSAGE_AS_KEY = "USE_MESSAGE_AS_KEY:";
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = new ArrayList<>();
for (FieldError fieldError : result.getFieldErrors()) {
String errorCode = null;
if (StringUtils.startsWith(fieldError.getDefaultMessage(), USE_MESSAGE_AS_KEY)) {
errorCode = StringUtils.removeStart(fieldError.getDefaultMessage(), USE_MESSAGE_AS_KEY);
}else {
errorCode = fieldError.getCode();
}
fieldErrors.add(new FieldErrorVM(fieldError.getObjectName(), fieldError.getField(), errorCode));
}
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", fieldErrors)
.build();
return create(ex, problem, request);
}
In a DTO you can now add this:
#AssertTrue(message = USE_MESSAGE_AS_KEY + "amountNot0RateAbove1")
public boolean isRate() {
return amount == 0 ? true : rate != null && rate > 0;
}
and it will use the message amountNot0RateAbove1 (prefixed with error.) as key to translate in the frontent to a nice error message.
I had similar issue - but I just wanted to output the #AssertTrue message in the response. I used this:
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(this::createFieldErrorVM)
.collect(Collectors.toList());
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", fieldErrors)
.build();
return create(ex, problem, request);
}
private FieldErrorVM createFieldErrorVM(FieldError fieldError) {
String errorCode;
if (fieldError.getCode().equals("AssertTrue")) {
errorCode = fieldError.getDefaultMessage();
}else {
errorCode = fieldError.getCode();
}
return new FieldErrorVM(fieldError.getObjectName(), fieldError.getField(), errorCode);
}

Debugging AJAX to Spring Controller (count of variables and spelling)

I am spending a lot of effort debugging ajax calls. The common issues are
number of parameters dont match from the ajax to the controller
spelling of the #RequestMapping parameter does not match
If the type does not match the call happens and it can be debugged. But the bigger issue is the above two. I have 88 parameters that I am passing and have a hell of a time trying to figure out what is missing or spelt incorrectly.
example
#RequestMapping("/saveClient")
public #ResponseBody String saveClientAJAXMethodView(#RequestParam(value = "clientName") String clientName,
.... 88 parameters more
$
.ajax({
type : "Post",
url : "saveClient",
data : {
clientName : clientName,
... 88 parameters more
I got this error
The request sent by the client was syntactically incorrect.
So I changed the signature of my controller to add , method = RequestMethod.POST).
Now I am getting
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource.
Its clearly a "POST" and still it get a request method GET not supported.
The question is NOT how to solve this problem. The question is how to debug such issues easily. What errors map to what issues, how to debug the 88 parameter spellings and count ? There must be a easier way to do this debugging.
I use the following
Debugging Mode of the controller
Inspect on Chrome.
Since you are sending huge amount of parameter in URL for POST request. I would suggest you to send your data in body.
For example if you are sending parameters like clientId, clientName, clientEmail etc.. you have used #RequestParam annotation to get individual parameter data in your controller:
String saveClientAJAXMethodView( #RequestParam String clientId,
#RequestParam String clientName,
#RequestParam String clientEmail
.... more parameters)
Instead of using #RequestParam I would suggest you to use #RequestBody, For this you need to create a Data transfer object (DTO) like this:
class ClientInfo{
String clientId,
String clientName,
String clientEmail,
....
.... other variables
.... getters and setters of variables
}
And then use this DTO in your controller method like this:
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
}
Using this approach you will not get any exception regarding spelling mistake or parameter missing .The value will be assigned to a DTO variable if you are sending value with right key as specified in DTO.
To count variables in ClientInfo object you will need to cast ClientInfo to JSONObject and use its size() method to get count of variables
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
JSONObject json = new JSONObject(clientInfo);
System.out.println(json.keySet().size());
}
Your ajax call will look like this:
var clientInfo = {
'clientName': 'tom',
'clientId': '23AZ1',
'clientEmail': 'xyz#gmail.com',
...
};
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(clientInfo),
contentType: "application/json",
complete: callback
});
I hope following steps would help you debug:
1- Use a filter to intercept request.
2- Create a custom annotation which would indicate that you want to debug this method.
3- Use the method defined in this post Can I get all of requestMapping URL with GET method in the Spring? and your custom annotation to store list of all methods which you want to debug in a singleton bean.
4- Now write some logic in filter which would print mismatch between the method parameters and request parameters.
CustomFilter:
public class CustomFilter extends GenericFilterBean {
#Autowired
#Qualifier("printMismatchMethods")
HashMap<String,Method> methodsToCheck;
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
HttpServletRequest r = (HttpServletRequest) request;
String url = r.getRequestURI().substring(r.getContextPath().length());
//Remove extensions if present any
int index = url.indexOf('.');
if(index > 0)
url = url.substring(0,url.indexOf('.'));
/*Matching string this should be replaced by url pattern matching of spring.*/
if (methodsToCheck.containsKey(url)){
Method method = methodsToCheck.get(url);
Map<String, String[]> requestParameterMap = r.getParameterMap();
Map<String,Boolean> isParamPresent = new HashMap<String,Boolean>();
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
if (!requestParam.name().isEmpty())
isParamPresent.put(requestParam.name(), false);
else
isParamPresent.put(requestParam.value(), false);
}
}
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
String name = null;
if (!requestParam.name().isEmpty())
name=requestParam.name();
else
name=requestParam.value();
if (requestParameterMap.containsKey(name)){
isParamPresent.put(name, true);
}
}
}
for (Map.Entry<String, Boolean> entry : isParamPresent.entrySet()){
if (!entry.getValue()){
System.out.println(entry.getKey() + " is either missing or mis-spelled");
}
}
}
chain.doFilter(request, response);
}
}
configured as follows:
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Declare following custom annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PrintParamMismatch {
}
Define following bean which would populate on startup
#Bean(name="printMismatchMethods")
#Autowired
public HashMap<String,Method> printParamMismatchMethods(BeanFactory beanFactory){
HashMap<String,Method> methods = new HashMap<String,Method>();
Map<String, RequestMappingHandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
(ListableBeanFactory)beanFactory,
RequestMappingHandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
ArrayList<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(handlerMappings);
RequestMappingHandlerMapping mappings = matchingBeans.get("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mappings.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethod : handlerMethods.entrySet()){
RequestMappingInfo info = handlerMethod.getKey();
HandlerMethod hMethod = handlerMethod.getValue();
Method method = hMethod.getMethod();
if (method.getAnnotation(PrintParamMismatch.class) != null){
String path = info.getPatternsCondition().toString();
path = path.substring(1,path.length());
path = path.substring(0,path.length()-1);
methods.put(path, method);
}
}
}
return methods;
}
This, I think is generic enough to show debug information for now. However we need to store and match Patterns instead of url string.
So I used divide and rule and solved my issue. I commented top half of the parameters and ran, to check if my controller gets called. It did. then I added 1/4th, then 1/8th and found that I missed a parameter.
That along with #ArsianAnjum's answer is good for debugging. #Aji's answer is the long term solution. I should be using that.

Spring-MVC using a Converter to load object from path variable, but need to return 404 for unfound

TL;DR - Is there a way to throw an error from a registered type converter during the MVC databinding phase such that it will return a response with a specific HTTP status code? I.e. if my converter can't find an object from the conversion source, can I return a 404?
I have a POJO:
public class Goofball {
private String id = "new";
// others
public String getName () { ... }
public void setName (String name) { ... }
}
and am using a StringToGoofballConverter to create an empty object when "new".equals(id) or try to load a Goofball from the database if it exists:
public Goofball convert(String idOrNew) {
Goofball result = null;
log.debug("Trying to convert " + idOrNew + " to Goofball");
if ("new".equalsIgnoreCase(idOrNew))
{
result = new Goofball ();
result.setId("new");
}
else
{
try
{
result = this.repository.findOne(idOrNew);
}
catch (Throwable ex)
{
log.error (ex);
}
if (result == null)
{
throw new GoofballNotFoundException(idOrNew);
}
}
return result;
}
That converter is used by spring when the request matches this endpoint:
#RequestMapping(value = "/admin/goofballs/{goofball}", method=RequestMethod.POST)
public String createOrEditGoofball (#ModelAttribute("goofball") #Valid Goofball object, BindingResult result, Model model) {
// ... handle the post and save the goofball if there were no binding errors, then return the template string name
}
This all works quite well insofar as GET requests to /admin/goofballs/new and /admin/goofballs/1234 work smoothly in the controller for both creating new objects and editing existing ones. The hitch is that if I issue a request with a bogus id, one that isn't new and also doesn't exist in the database I want to return a 404. Currently the Converter is throwing a custom exception:
#ResponseStatus(value= HttpStatus.NOT_FOUND, reason="Goofball Not Found") //404
public class GoofballNotFoundException extends RuntimeException {
private static final long serialVersionUID = 422445187706673678L;
public GoofballNotFoundException(String id){
super("GoofballNotFoundException with id=" + id);
}
}
but I started with a simple IllegalArgumentException as recommended in the Spring docs. In either case, the result is that Spring is returning a response with an HTTP status of 400.
This makes me think I'm misusing the Converter interface but that approach appears to be recommended by the #ModelAttribute docs.
So, again the question: is there a way to throw an error from a registered type converter during the databinding phase such that it will return a response with a specific HTTP status code?
Answering my own question:
Change StringToGoofballConverter to simply return null for the unfound entity instead of throwing IllegalArgumentException or a custom exception. The #Controller method will then be given a Goofball object that has a null id (e.g. the id is not "new" nor the path element value). At that point I can throw a GoofballNotFoundException or any other #ResponseStatus exception from there, within the controller method to affect the response status code.

Spring RestRemplate postforobject with request parameter having integer value

I have a method in Spring rest service.
#RequestMapping(value = "test/process", method = RequestMethod.POST)
public #ResponseBody MyResponse processRequest(String RequestId, int count)
I am using Spring RestTemplate to call this service like this.
RestTemplate restTemplate = this.getRestTemplate();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", RequestId);
map.add("count", count);
restTemplate.postForObject(url, map,MyResponse.class);
When I try to invoke the client method I get the exception that no suitable HttpMessageConverter found for request type [java.lang.Integer]
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [java.lang.Integer]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:310)
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:270)
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:260)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:200)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1)
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:596)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:444)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:287)
I know one of the ways is to pass all the parameters as String. But I might need to pass complex data types as parameters later.
What is the ways to achieve this.
I have googled and some option seem to be writing my own converters. How should I start about solving this problem.
The root cause of this error is that by specifying an Integer in the LinkedMultiValueMap, the RestTemplate will take that to mean that your request is a multipart request. There is no HttpMessageConverter registered by default that can handle writing values of type Integer to a request body.
As you said, you can handle this situation by changing the count to be a String. After all, there is no Integer type in HTTP request parameters. However, you were worried
But I might need to pass complex data types as parameters later.
Assume something like this
public #ResponseBody MyResponse processRequest(String RequestId, int count, Complex complex) {
with
public class Complex {
private String someValue;
private int intValue;
public String getSomeValue() {
return someValue;
}
public void setSomeValue(String someValue) {
this.someValue = someValue;
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
public String toString() {
return someValue + " " + intValue;
}
}
The the following will work just fine
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", "asd");
map.add("count", "42");
map.add("someValue", "complex");
map.add("intValue", "69");
restTemplate.postForObject(url, map,MyResponse.class);
Remember that the request parameters are used to populate the fields of model attributes by their names.
An even better solution would have you using a serialization standard like JSON or XML.

Resources