I'm new to Spring, and since Spring provides many ways to map an HTTP request to Java objects, I'm hoping someone could advice me how to resolve this:
I have a client that sends a request having
ContentType: application/x-www-form-urlencoded
Some of the request parmeters have names such as
"form_data[orderStatus]", "form_data[orderNumber]", etc'
I have no control over this client!
I have a java class (#Component) called MyOrder which looks as follows:
#component
#Scope("prototpe")
public class MyOrder {
private String orderStatus;
private String orderNumber;
//etc'
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
//public setter for all other properties, as the above
}
What is the simplest way to create an instance of MyOrder
populated with all values of all "form_data[]", so that I can have a controller method having a signature that includes a MyOrder parameter, such as:
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
#ModelAttribute("myOrder") MyOrder anOrder) {
//... impl'n here
}
The best solution I could think of was to use a Web Filter which would flaten request params names such as "form_data[attrib1]" to "attrib1", and then Spring would do all the work of populating the MyOrder instance.
One disadvantage of this is that the request may have both "form_data[attrib1]" and "attrib1" parameters. For example:
form_data[orderStatus]=ok
orderStatus=fail
In this case i want MyOrder.orderStatus to have the value "ok".
Any good way of utilizing Spring create MyOrder from the request?
As an alternative, that does not use the class MyOrder, is there a way to have Spring map all the form_data[] parameters and their values to a map, so that i can have the controller method below?
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
<some annotation> #Map<String,String> formFieldsOfAnOrder) {
//... impl'n here
orderStatus = formFieldsOfAnOrder.get("orderStatus");
//or at least:
orderStatus = formFieldsOfAnOrder.get("form_data[orderStatus]");
}
Related
I have big legacy project with a lot of code and logic.
I have many similar methods in my controller:
public void someEndpoint(
#RequestHeader("flowId") String flowId,
#RequestHeader("someAnotherParam") String someAnotherParam,
#RequestHeader("customerId") String customerId
) {
//pass all arguments to services
}
Every controller method has this three arguments.
This three arguments are passed to another services, and next to another services and another services as method argument.
Whole code is a little messy from this reason.These three arguments are everywhere.
Can I write something like a provider for this three parameter? Some service like:
#Service
class RequestContextProvider {
public RequestContext getRequestContext() {
//some logic
}
}
class RequestContext {
String flowId,
String someAnotherParam,
String customerId
}
And how to do that using spring?
You can use RequestContextHolder class as below:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
String flowId = request.getHeader("flowId");
I need to build an object before it even reaches the controller and one way I found to do that is by using HandlerMethodArgumentResolver.
Basically, I have a pojo which gets mapped to the request parameters, but I want to set some other fields in that pojo before it reaches the controller.
POJO: UserParams.java
#AllArgsConstructor
public class UserParams {
private String firstName;
private String lastName;
private String sessionId;
}
Let's say my request comes in as localhost:8080/user?firstName=John&lastName=Doe
So, in my resolver I want to bind the UserParams object using the request params from the above request and populate sessionId field and return the bound object with additional value.
#Component
public class UserParamsResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(UserParams.class);
}
#Override
public Object resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory) {
WebDataBinder binder = new WebDataBinder(BeanUtils.instantiateClass(parameter.getParameterType()));
ServletRequestParameterPropertyValues values = new ServletRequestParameterPropertyValues(((ServletWebRequest) webRequest).getRequest());
binder.bind(values);
BindingResult result = binder.getBindingResult();
// UserParams userParams = how to get this object?
// userParams.setSessionId(userParams.getLastName + Math.random())
return userParams;
}
So, when the request eventually reaches the controller, I've the userParams with sessionId in it.
I tried looking at many places (programcreek.com had lot of examples for WebDataBinder) and tried to find out how Spring binds the objects with request params before coming to the Controller, but I had no luck.
One solution is to use spring AOP in order to process all #Controller methods having UserParams as a parameter. Then you have to get access to WebRequest in order to get sessionId and this is a little bit trickier; you'll have to create a #Bean #Scope("request") (e.g. name it WebRequestAccessor) which to contain an #Autowired field of type WebRequest. Autowire that WebRequestAccessor bean into your #Aspect in order to use its WebRequest field which then gives you access to sessionId. Set the sessionId on the UserParams parameter then let the advised method continue its work.
Take a look here about how to write & use an #Aspect.
UPDATE
You could use JSESSIONID instead of HttpSession.getId(). Just annotate private String sessionId with #CookieValue("JSESSIONID").
UPDATE 2
I'm pretty sure you should use ServletModelAttributeMethodProcessor (or ModelAttributeMethodProcessor) instead of HandlerMethodArgumentResolver.
I have something like this :
#RestController
#RequestMapping("/prop")
public class PropController {
#RequestMapping(method = RequestMethod.POST)
public Prop getProp(#ModelAttribute PropForm propForm) {
//calling methods and stuff using propForm
}
}
My PropForm class :
#Data
public class PropForm {
private String att1;
private String att2;
private String att3;
}
Now I am calling this URL :
http://localhost:8080/prop?att1=blabla&att2=blob&att3=test
I want to extract the parameters from the URL and put them in my propForm.
I've tried replacing #ModelAttribute by #RequestBody and then by #RequestParam. It's still not working, I always get a NullPointerException when running the application.
Please, note that I need to use POST method. I already have it working using GET method
FIRST Make sure you have getters and setters in your PropForm class...
Then, you need to put into your model the Form entity:
model.put("NAME", propForm);
And declare method like this:
#RequestMapping(method = RequestMethod.POST)
public Prop getProp(
#ModelAttribute PropForm propForm
#Valid #ModelAttribute("NAME") PropForm propForm)
// ^ you're missing the name!
{
// do your stuff....
return (Prop) propForm;
}
I think you controller and mapping is ok.
But the problem is you are expecting a post request in the mapping, and you are calling
http://localhost:8080/prop?att1=blabla&att2=blob&att3=test
Now this will generate a GET request Not Post. You cannot send a post request using only url.
If you cant use a form for sending the request then you need to use any 3rd party to generate a POST request
like you can use jquery $.post()
And also att1 att2 will not help unless you bind the object with the model attribute.
I am trying to make a post call to a controller, but the object I am expecting contains a Set datatype and I am unsure how the post data should look.
Models:
public class Notebook{
private string name;
private Set<Todo> todos;
}
public class Todo{
private String name;
}
Controller
#RequestMapping(method = RequestMethod.POST)
public void createNotebook(Notebook q){
questionnaireService.saveOrUpdateNotebook(q);
}
Currently I have tried posting like the example below:
curl --data "name=some notebook&todos[0].name=todo1&todos[1].name=todo2" http://localhost:8080/api/notebook
Doesn't seem to work. Anyone have experience with Sets?
You should qualify Notebook q with #RequestBody annotation so that the request can be mapped to an object of type Notebook. More about the format of the input data and the converters in Spring MVC doc: Mapping the request body with the #RequestBody annotation.
We send data from the front-end in JSON format and use Jackson JSON to convert it to the Java object. If you go that route, you can directly declare the todos as Set<String> and the input would be
{
name: "some notebook",
todos: ["todo1", "todo2"]
}
I've went thru Spring documentation and source code and still haven't found answer to my question.
I have these classes in my domain model and want to use them as backing form objects in spring-mvc.
public abstract class Credentials {
private Long id;
....
}
public class UserPasswordCredentials extends Credentials {
private String username;
private String password;
....
}
public class UserAccount {
private Long id;
private String name;
private Credentials credentials;
....
}
My controller:
#Controller
public class UserAccountController
{
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid UserAccount account)
{
//persist in DB
return account.id;
}
#RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
public String listAccounts()
{
//get all accounts from DB
return "views/list_accounts";
}
....
}
On UI I have dynamic form for the different credential types. My POST request usually looks like:
name name
credentials_type user_name
credentials.password password
credentials.username username
Following exception is thrown if I try to submit request to the server :
org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)
My initial thought was to use #ModelAttribute
#ModelAttribute
public PublisherAccount prepareUserAccountBean(#RequestParam("credentials_type") String credentialsType){
UserAccount userAccount = new PublisherAccount();
Class credClass = //figure out correct credentials class;
userAccount.setCredentials(BeanUtils.instantiate(credClass));
return userAccount;
}
Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.
One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.
Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?
Thank you!!!!!
I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.
An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.
So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :
Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).
I'm not sure, but you should be using ViewModel classes on your controllers instead of Domain Objects. Then, inside your saveAccount method you would validate this ViewModel and if everything goes right, you map it into your Domain Model and persist it.
By doing so, you have another advantage. If you add any other property to your domain UserAccount class, e.g: private bool isAdmin. If your web user send you a POST parameter with isAdmin=true that would be bind to user Domain Class and persisted.
Well, this is the way I'd do:
public class NewUserAccount {
private String name;
private String username;
private String password;
}
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid NewUserAccount account)
{
//...
}