I have a Spring MVC controller. And I have in the method 50 parameters. All of the parameters have very specific name, for example: FOO[].
I don't want write 50 parameters in the method signature like this:
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody
public String test(
#RequestParam(value = "FOO[]") String foo,
#RequestParam(value = "BAR[]") String bar,
// Other 48 parameters
)
{
return "test";
}
I want to map all the parameters on the one object, I mean, I want to write a simple bean class with getter/setter and use it like method parameter.
But how can I set custom names to my class fields?
e.g.:
class FooBar {
#SomeAnnotation_for_binding_the_field_to_my_field_FOO[]
private String foo;
private String bar;
// Other 48 fields
// getters/setters
}
I know annotations are kinda cool, but think rationally. You HAVE to enumerate, in code, all the mappings. There is nothing implicit about mapping FOO[] to foo, it seems to be beyond your control. Just take the parameters as a map (you can always ask Spring to give you map of all parameters) and do something like:
#RequestMapping
public String test(#RequestParam Map<String, Object> map) {
MyObject mo = new MyObject();
mo.setFoo(map.get("FOO[]").toString());
mo.setBar(map.get("WOBBLE13[][]").toString);
return "whatever";
}
If you want to make this process more automatic, and if there exists an alorithm that maps parameter names to property names, you can use Spring's bean wrapper:
#RequestMapping
public String test(#RequestParam Map<String, String> map) {
BeanWrapper bw = new BeanWrapperImpl(new MyObject);
for (Entry<String, Object> entry : map.entrySet()) {
bw.setProperty(entry.getKey(), entry.getValue());
}
}
private static String decodeName(String n) {
return n.toLowerCase().substring(0,n.length() - 2);
}
You could make the process even more automatic by using a different Binder, you could (really, not a problem) add some custom annotations... but really, there is no point, if you just have a single case of 50 params. If you insist, add a comment.
THis sounds like a good time to use a hashmap, with key as the var name and value as value. Wrap that in a form backing object.
You could have a resource class i.e FooBarResource.jave and in your controller use that as a request body something like the following:
#ResponseBody
#RequestMapping(value = "/test", method = RequestMethod.POST)
#Secured({"ROLE_ADMIN"})
public ResponseEntity<ModelMap> createTest(#Valid #RequestBody FooBarResource body, UriComponentsBuilder builder) {
Related
I've RESTful service Spring MVC based.
The service has a RESTful resource method that returns the following response:
public class OperationalDataResponse<T> {
private String status;
private String statusMessage;
private T result;
//getters and setters
}
This response object encapsulates the result object of type T.
On the client side I use RestTemplate with GsonHttpMessageConverter added.
I get the response from service as a ResponseEntity
I handle the generic response with runtime Type as below:
public class OperationalDataRestClient<REQ,RESULT_TYPE> {
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, Class<RESULT_TYPE> resultType) {
//code to invoke service and get data goes here
String responseString = responseEntity.getBody();
response = GsonHelper.getInstance().fromJson(responseString, getType(OperationalDataResponse.class, resultType));
}
Type getType(final Class<?> rawClass, final Class<?> parameter) {
return new ParameterizedType() {
#Override
public Type[] getActualTypeArguments() {
return new Type[] { parameter };
}
#Override
public Type getRawType() {
return rawClass;
}
#Override
public Type getOwnerType() {
return null;
}
};
}
}
This works like a charm as long as my resultType is a non-collection class.
So, this works great from caller code:
getOperationalData(someResourcePath, someUrlVariables, MyNonGenericClass.class)
However if my resultType is a collection (say, List<String> or List<MyNonGenericClass>)
then I don't know how to pass the resultType Class from the caller code.
For example, from caller code,
getOperationalData(someResourcePath, someUrlVariables, List.class)
or
getOperationalData(someResourcePath, someUrlVariables, List<MyNonGenericClass>.class)
throws compilation error.
I tried passing on ArrayList.class as well but that too doesn't work.
Any suggestion how can I pass a generic collection as a resultType from caller code (in other words, as an example, how can I pass the class object of a List<String> or List<MyNonGenericClass> from caller code ?)
If you know that ResultType is coming as a List, Then it will obvious fail like you said compilation issue.Why? because you are trying to send a List when you method only accepts a single value.In order to over come that issue you will have to change the method arguments to the following
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, List<Class<RESULT_TYPE>> resultType){
....
}
and you will have to make some slight modification to getType() Method,loop it and then pass each class value to getType method like so
for(MyNonGenericClass myClass:mylist){
getType(OperationalDataResponse.class, myClass.getClass());
}
I have a controller that accepts a model UpdateProductCommand like this:
public IHttpActionResult UpdateProduct(UpdateProductCommand command)
{
command.AuditUserName = this.RequestContext.Principal.Identity.Name;
// ....
}
For security issues, the AuditUserName field should never be set outside (from the API call).
How can I remove (or truncate) the value of this field from JSON request?
It can be achieved by a following ModelBinder:
using Newtonsoft.Json.Linq;
public class FieldRemoverModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
JObject json = JObject.Parse(content);
JToken property = json.GetValue(bindingContext.ModelName, StringComparison.OrdinalIgnoreCase);
property?.Parent.Remove();
bindingContext.Model = json.ToObject(bindingContext.ModelType);
return true;
}
}
Use it like this:
public IHttpActionResult UpdateProduct(([ModelBinder(typeof(FieldRemoverModelBinder), Name = nameof(UpdateProductCommand.AuditUserName))]UpdateProductCommand command)
{
// here command.AuditUserName will always be empty, no matter what's in json
That's what DTOs are for.
You can just create another class (UpdateProductCommandDto for example) that has only the properties you need / want to be used as the input, and then you can just use something like Automapper to map it to a new instance of UpdateProductCommand.
I have a Spring MVC survey application where the Controller method called by each form POST is virtually identical:
#PostMapping("/1")
public String processGroupOne (
Model model,
#ModelAttribute("pageNum") int pageNum,
#ModelAttribute(GlobalControllerAdvice.SESSION_ATTRIBUTE_NAME) #Validated(SurveyGroupOne.class) SurveyCommand surveyCommand,
BindingResult result) {
if (result.hasErrors()) {
LOG.debug(result.getAllErrors().toString());
model.addAttribute("pageNum", pageNum);
return "survey/page".concat(Integer.toString(pageNum));
}
pageNum++;
model.addAttribute("pageNum", pageNum);
return "redirect:/survey/".concat(Integer.toString(pageNum));
}
The only difference is what part of the SurveyCommand object is validated at each stop along the way. This is designated by the marker interface passed to the #Validated() annotation. The marker interfaces (SurveyGroupOne, SurveyGroupTwo, etc) are just that, markers:
public interface SurveyGroupOne {}
public interface SurveyGroupTwo {}
...
and they are applied to properties of objects in the SurveyCommand object:
public class Person {
#NotBlank(groups = {
SurveyGroupTwo.class,
SurveyGroupThree.class})
private String firstName;
#NotBlank(groups = {
SurveyGroupTwo.class,
SurveyGroupThree.class})
private String lastName;
...
}
My question: how can I make the method generic and still use the marker interface specific to the page being processed? Something like this:
#PostMapping("/{pageNum}")
public String processGroupOne (
Model model,
#PathVariable("pageNum") int pageNum,
#ModelAttribute(GlobalControllerAdvice.SESSION_ATTRIBUTE_NAME)
#Validated(__what goes here??__) SurveyCommand surveyCommand,
BindingResult result) {
if (result.hasErrors()) {
LOG.debug(result.getAllErrors().toString());
model.addAttribute("pageNum", pageNum);
return "survey/page".concat(Integer.toString(pageNum));
}
pageNum++;
model.addAttribute("pageNum", pageNum);
return "redirect:/survey/".concat(Integer.toString(pageNum));
}
How can I pass the proper marker interface to #Validated based solely on the pageNum #PathVariable (or any other parameter)?
Because #Validated is an annotation, it requires its arguments to be available during compilation and hence static. You can still use it but in this case you will have N methods, where N is a number of steps. To distinguish one step from another you can use params argument of #PostMapping annotation.
There is also another way where you need to inject Validator to the controller and invoke it directly with an appropriate group that you need.
In Spring controller, I want to invoke same method for different HTML - Forms submission
So, taking HttpServletRequest as a RequestBody
#RequestMapping(value = "/Search")
public String doSearch(HttpServletRequest httpServletRequest, ModelMap map) {
// Now, looking for something like this...
if(req.getType.equals("x")
//X x = SOME_SPRING_UTIL.convert(httpServletRequest,X.class)
else
// Y y = SOME_SPRING_UTIL.convert(httpServletRequest,Y.class)
}
I want to convert request parameters to bean through Spring, As it converts while I take Bean as method argument
Use the params attribute of the #RequestMapping annotation to differentiate the request mapping mapping.
#RequestMapping(value="/search", params={"actionId=Actionx"})
public String searchMethod1(X search) {}
#RequestMapping(value="/search", params={"actionId=ActionY"})
public String searchMethod2(Y search) {}
This way you can create methods for each different action and let spring do all the heavy lifting for you.
I was just wondering how to pass in post parameters such as following exercept from html options, normally i would get a array in language such as php (POST['param'][0]... would work i believe)
url?param=value1¶m=value2¶m=value3
I tried:
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(String[] param)
But this doesn't work for some reason...
Any ideas would be greatly appreciated!
You can use this:
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(#RequestParam(value = "param[]") String[] paramValues){...}
it will retrieve all values (inside array paramValues) of parameter param (note the attribute value of RequestParam: it ends with [])
This should work:
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(#RequestParam("param") String[] param)
If anyone is still struggling with that, this is how it should be done:
Form inputs:
<input name="myParam" value="1"/>
<input name="myParam" value="4"/>
<input name="myParam" value="19"/>
Controller method:
#RequestMapping
public String deletePlaces(#RequestParam("myParam") Long[] myParams) {
//myParams will have 3 elements with values 1,4 and 19
}
This works the same for String[] Integer[] Long[] and probably more. POST,GET,DELETE will work the same way.
Parameter name must match name tag from form input. No extra [] needed etc. In fact, param name can be ommited, if method argument name is the same as input name so we can end up with method signature like this:
#RequestMapping
public String deletePlaces(#RequestParam Long[] myParams)
and it will still work
Something extra:
Now if you have domain model lets say Place and you have PlaceRepository, by providing Place#id as value of your inputs, Spring can lookup related objects for us. So if we assume that form inputs above contais User ids as values, then we can simply write this into controller:
public String deletePlaces(#RequestParam Place[] places) {
//places will be populated with related entries from database!
}
Sweet isn't it?
If you know your param name, try
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(#RequestParam("myParam") String param)
Another way is to use the current request object:
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(HttpServletRequest request) {
Map parameterMap = request.getParameterMap();
...
}
#RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(#RequestParam(value = "param[]") String[] paramValues {...}
This will work when you send for example ajax by jQuery:
$.ajax({
type: "POST",
data: { param:paramValues }
...
});
but when you send only single element array with String, which contains commas like "foo,baa", Spring will split this String and create array with 2 elements "foo" and "baa".
So be careful with commas when your array can have also one element.
1, add a java class as requestBody
public class PostMembers {
private String[] members;
public String[] getMembers() {
return members;
}
public void setMembers(String[] members) {
this.members = members;
}
}
2, add a method in controller class
#PostMapping("")
public List<SomeClass> addMember(#RequestBody PostMembers postMembers) {
// ...
}
now your controller can get array params .
3, test with curl
curl -H "Content-Type:application/json" \
-X POST -d '{"members":["item1","item2"]}' \
http://localhost:8080/api/xxx
or if you use vue
axios.post(someUrl,{
members:[
'item1','item2'
]
}).then(response =>{
// ...
})