I have following issue: we've been developing website using Spring tools 3.8.3 and one of the option is to allow users to reset their passwords. In order to do that they need to specify their email address and validate captcha. When these steps are completed data is sent to ResetPassword controller which does all necessary checks and work. Controller has following address:
website.com/path/resetPassword/
And has following structure where it extends abstract controller template to define the input and output beans:
#RequestMapping(path="website.com/path/resetPassword/",
consumes = "application/JSON",
produces = "application/JSON",
method = "POST")
public class ResetPassController extends ControllerTemplate<ResetPassCaptchaBean, ResponseBean>{
// Autowired field declaration
// Couple of methods calling services
}
The bean also has only two fields - email and captcha accordingly. Captcha field uses annotation to check if either it's valid or not:
public class ResetPassCaptchaBean extends ResetPassBean {
#CaptchaValid
private String captcha;
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public String getCaptcha() {
return captcha;
}
}
public class ResetPassBean {
private String email;
public void setEmail(String email) {
this.email= email;
}
public String getCaptcha() {
return email;
}
}
Now we are adding mobile API and it's necessary to do the same procedure but without using captcha validation. Therefore we're simply using the bean without captcha field. Since captcha is validated using the annotation in the bean it's completely not used in the controller itself and therefore we'd like to have the same controller both for handling requests from web site and from mobile API.
I understand that it's possible to create two different controllers and move all the implementation to service layer (read other class) but then there still would be two identical classes with only one line difference
public class ResetPassController extends ControllerTemplate<ResetPassCaptchaBean, ResponseBean>{
//Autowiring same stuff
//Calling same methods
}
and
public class MobileResetPassController extends ControllerTemplate<ResetPassBean, ResponseBean>{
//Autowiring stuff
//Calling same methods
}
So the question is: leave it with two identical controllers or is there other solution?
Thanks!
Related
I m working on this project where am I trying to access two different controllers with the same URI. After trying to run it I m getting a BeanCreationException.
So it happens that I m getting an Error while creating a bean.
I hope there is a way to deal with this.
The error message that I m getting:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'requestMappingHandlerMapping' defined in
class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalStateException: Ambiguous mapping. Cannot map
'userController' method public java.lang.String
com.javalanguagezone.interviewtwitter.controller.UserController.overview(java.security.Principal,org.springframework.ui.Model)
to {[/overview],methods=[GET]}: There is already 'tweetController'
bean method
I m using as well Thymleaf for this project. The URI that I m for those two controllers: http://localhost:8080/api/overview.The two controllers are providing my Thymleaf page with information that i have to present at the same time with the URI just mentioned. With this, I m calling both controllers but I m getting a previously mentioned error.
The first controller class(TweetController):
#Controller
#Slf4j
public class TweetController {
private TweetService tweetService;
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
#GetMapping( "/overview")
public String tweetsFromUser(Principal principal, Model model) {
model.addAttribute("tweets",tweetService.tweetsFromUser(principal).size());
return "api/index";
}
}
The second controller class is:
#Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/followers")
public String followers(Principal principal) {
userService.getUsersFollowers(principal);
return "api/index";
}
#GetMapping("/following")
public int following(Principal principal) {
return userService.getUsersFollowing(principal);
}
#GetMapping("/overview")
public String overview(Principal principal, Model model){
model.addAttribute("followers",userService.getUsersFollowers(principal));
model.addAttribute("following",userService.getUsersFollowing(principal));
return "api/index";
} }
My question: is there a way a fix it or I to look for another way around? I m relatively a newbie with Spring. Thank you for your help in advanced.
according to REST conventions, you should not have /overview, but /user/overview. You can set it by supplying #RequestMapping("/user") in your userController.
In the same way you would have "/tweet/overview" endpoint.
#Controller
#Slf4j
#RequestMapping("/tweet")
public class TweetController {
doing it any other way is against conventions, against Spring Rules and probably means you're doing something wrong. Spring does not allow two methods with same uri because it does not know which method exactly you would want to call.
upd: if you need logic, you can send parameters to GET: /overview?customParam=user
#GetMapping( "/overview")
public String tweetsFromUser(#RequestParam(value="customParam") String
param, Principal principal, Model model) {
// logic checking customParam...
But that CANNOT be in two different controllers. The only way to specify the controller, is through base-uri and parameters are not part of it.
Spring determines the method by 2 parameters: Mapping and HTTP method. There is no way, unless you modify Spring manually, to allow 3rd parameter in this case. Also, there is no 3rd parameter.
Alternatively, you can have 3rd controller with Mapping, that calls other 2 controllers when "/overview" endpoint is triggered. In that case, you need to remove the mapping from tweet and user - controllers.
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]");
}
I'm using Spring 4.3.7, and I got two form controllers with forms rendering using Spring form taglib in corresponding views. To preserve form data between requests (for rendering invalid forms) I store them in SessionAttributes.
LabCreateController:
#Controller
#RequestMapping(path = "/labs/create")
#SessionAttributes("form")
public class LabCreateController {
#ModelAttribute("form")
public LabCreateForm form() {
return new LabCreateForm();
}
#GetMapping
public String showForm() {
return "lab_create";
}
}
WallController:
#Controller
#RequestMapping(path = "/group/{id}/wall")
#SessionAttributes("form")
public class WallController {
#ModelAttribute("form")
public PostCreateForm form() {
return new PostCreateForm();
}
#GetMapping(path = "/new")
public String newPostGet() {
return "communities_newpost";
}
}
I open /labs/create in browser, everything is fine. Then I open /group/4/wall/new and get a following error:
Invalid property 'text' of bean class
[...LabCreateForm]
i.e it means that attribute form from LabCreateController somehow passed to WallController, though Spring documentation says:
Session attributes as indicated using this annotation correspond to a
specific handler's model attributes.
I believe it means they shouldn't be shared between controllers. Also this answer says that it is so since Spring 3.
Is it a bug or I'm missing something? If not, what is the appropriate way of storing a form inside one controller?
I have a domain object class User (it is a JPA entity):
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
And I am trying to offer a REST API to allow clients to create new users, using Spring 3 MVC:
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody User user) {
user.setEnabled(true); // client is not allowed to modify this field
userService.createUser(user);
...
}
}
It works great, but I do not know if it is a good idea to use the domain objects as #RequestBody, because I have to protect some fields that should not be directly modified by the client (i.e. "enabled" in this case).
What are the pros/cons of these alternatives:
Use the domain objects and protect the fields the user is not allowed to modify (for example set them to null or to its default value by hand)
Use a new set of auxiliar objects (something similar to a DTO), such as a UserRequest that only contains the fields I want to expose through the REST API, and map them (i.e. with Dozer) to the domain objects.
The second alternative looks like this:
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
public class UserRequest {
private String name;
// enabled is removed
// getters/setters
}
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody UserRequest userRequest) {
User user = ... // map UserRequest -> User
userService.createUser(user);
...
}
}
Is there any other way that avoids code duplication and is easier to maintain?
There is another option - you can disallow the submission of a given set of properties, using the DataBinder.setDisallowedFields(..) (or using .setAllowedFields(..))
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(..);
}
This is fine if you have one or two properties that differ.
Otherwise, having a special object (like ProfileDetails or UserRequest) makes more sense. I am using such a DTO-like object for this scenario and then transfer the fields with BeanUtils.copyProperties(..) from commons-beanutils
A third, perhaps better option, is to put all profile-related fields into a separate entity (mapped with #OneToOne with user) or to an #Embeddable object, and use it instead.
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)
{
//...
}