How to bind subclass object in spring form submit as modelAttribute - java

I Have
Class Shape {
//Implementation
}
Class Round extends Shape{
//Implementation
}
Controller
I Have
#Requestmapping(value="/view/form")
public ModelAndView getForm(){
ModelAndView mav=new ModelAndView();
mav.addObject("shape",new Round());
}
#RequestMapping(value="/submit", method = RequestMethod.POST)
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape){
if(shape instanceof Round){ //**Not giving positive result**
}
}
in Jsp
<form:form action="/submit" commandName="shape" method="post">
//form tags
</form:form>
when I submit the form with Round object. At controller side ModelAttribute is not giving instance of Round . its giving instance of shape only. How to do this

this will never work
<form:form action="/submit" commandName="shape" method="post">
you are submitting a shape from the form and expecting a shape
in the controller method
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape)
which will never give you an Round object.
simply submit a Round object from the form and use that.
<form:form action="/submit" commandName="round" method="post">
public ModelAndView submitForm(#ModelAttribute("round") Round round)
edited :-
have a hiddenInput type in the form which will tell controller the type of Shape it is passing, you can change the value of hidden tag dynamically
upon user request.
<input type="hidden" name="type" value="round">
get the value of the type inside contoller and use it to cast the Shape object
public ModelAndView submitForm(
#ModelAttribute("shape") Shape shape,
#RequestParam("type") String type)
{
if(type.equals("round")){
//if type is a round you can cast the shape to a round
Round round = (Round)shape;
}
}

You cannot do this. Because those are two different request lifecycles.
#Requestmapping(value="/view/form")
public ModelAndView getForm(){
ModelAndView mav=new ModelAndView();
mav.addObject("shape",new Round());
}
When above request executes, even if you added Round object in mav, it has been converted into html response and sent back to client.
So when below request comes next when you submit the form, it's a totally separate request and spring has no way to identify which object type was added for previous request.
#RequestMapping(value="/submit", method = RequestMethod.POST)
public ModelAndView submitForm(#ModelAttribute("shape") Shape shape){
if(shape instanceof Round){ //**Not giving positive result**
}
}
But you can try exploring #SessionAttributes, using this you might be able to maintain the same object across different requests

Its possible to specify the subclass that you need to bind to. You have to add an additional parameter (hidden input) in your form which specify the type that needs to be bound to. This field must have the same name as the model attribute in this case shape. You then need to implement a converter that converts this string parameter value to the actual instance that you need to bind to
The following are the changes that you need to implement
1)In your jsp add the hidden input inside your
<form:form action="/submit" commandName="shape" method="post">
<input type="hidden" name="shape" value="round"/>
//other form tags
</form:form>
2)Implement a Converter to convert from String to a Shape
public class StringToShapeConverter implements Converter<String,Shape>{
public Shape convert(String source){
if("round".equalsIgnoreCase(source)){
return new Round();
}
//other shapes
}
}
3) Then register your converter so that Spring MVC knows about it. If you are using Java config you need to extends WebMvcConfigurerAdapter and override the addFormatters method
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new StringToShapeConverter());
}
}
If you are using xml configuration you can use the mvc:annotation-driven element to specify the conversion-service to use. Then register your converter using the FormattingConversionSErviceFactoryBean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="some.package.StringToShapeConverter"/>
</set>
</property>
</bean>
</beans>

Related

Adding attribute on registration page using Hybris setup

I have added a new attribute Pan (pancard number) on the registration form. Validations are also working fine. But if I hit the register tab, I get an error like
de.hybris.platform.servicelayer.exceptions.ModelSavingException: [de.hybris.platform.servicelayer.interceptor.impl.MandatoryAttributesValidator#246420ba]:missing values for [pan] in model CustomerModel () to create a new Customer] with root cause de.hybris.platform.servicelayer.interceptor.impl.MandatoryAttributesValidator$MissingMandatoryAttributesException: [de.hybris.platform.servicelayer.interceptor.impl.MandatoryAttributesValidator#246420ba]:missing values for [pan] in model CustomerModel () to create a new Customer
To resolve this error I tried modifier optional = "true" in items.xml doing that above error got resolved but I am not able to store the value for the pan.
So please help me to solve both issues customermodel error and storing value in the database.
You need to extend RegisterForm, RegisterData, RegistrationPageController and CustomerFacade to transfer new field value to model.
Before I jump into the solution, I'd like to clarify my approach:
A new change request (CR) is needed in the default (OOTB) registration functionality (or it can be any other functionality in SAP CC). The CR includes UI and data model changes. Basically, adding new field in the registration form, then passing it to CustomerModel to persist it in the database.
Now to the solution:
I'm assuming that you managed to add the new form registration field in the UI, hence, I'm proceeding with java code side.
The method for the registration is in LoginPageController.java class.
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String doRegister(#RequestHeader(value = "referer", required = false)
final String referer, final RegisterForm form, final BindingResult bindingResult, final Model model,
final HttpServletRequest request, final HttpServletResponse response, final RedirectAttributes redirectModel)
throws CMSItemNotFoundException
{
getRegistrationValidator().validate(form, bindingResult);
return processRegisterUserRequest(referer, form, bindingResult, model, request, response, redirectModel);
}
First of all, you need to add the new attribute pan to RegisterForm.java by extending the original form.
public class CustomRegisterForm extends RegisterForm
{
private String pan;
public String getPan() {
return pan;
}
public void setPan(String pan) {
this.pan = pan;
}
}
And you need to replace the parameter final RegisterForm form with final CustomRegisterForm formin above doRegister method.
Next, you need to add a new property pan to RegisterData bean in custom-beans.xml file.
<bean class="de.hybris.platform.commercefacades.user.data.RegisterData">
<property name="pan" type="String"/>
</bean>
Then you need to write your a custom processRegisterUserRequest method that has the parameter final CustomRegisterForm form instead of final RegisterForm form.
The content of the method will be almost the same with adding one extra line to set the value of the new attribute pan in RegisterData.
final RegisterData data = new RegisterData();
...
...
data.setPan(form.getPan());
Lastly, you need to extend the default implementation of DefaultCustomerFacade.java (let's say DefaultCustomCustomerFacade.java) and override the bean definition in custom-spring.xml file.
<alias name="defaultCustomCustomerFacade" alias="b2bCustomerFacade"/>
<bean id="defaultCustomCustomerFacade" class="xxx.yyy.uuu.DefaultCustomCustomerFacade" parent="defaultB2BCustomerFacade">
</bean>
In your DefaultCustomCustomerFacade.java you mainly will override two methods which are register and setCommonPropertiesForRegister
In setCommonPropertiesForRegister you will set the value of the new attribute pan in CustomerModel.
protected void setCommonPropertiesForRegister(final RegisterData registerData, final CustomerModel customerModel)
{
...
...
...
customerModel.setPan(registerData.getPan());
}
As the CustomerModel.Pan value is set now, when the CustomerModel is saved, the value of pan will be persisted in the database.

Atribute for annotations #ModelAtribute and #PreAutorize in Generic Controller / Service classes

I am trying setting a generic class for my project. So far I get this code:
https://github.com/klebermo/blog.cms/blob/master/src/main/java/com/config/generic/controller/GenericController.java
In this moment, I am stuck with this method:
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
public String cadastra(#ModelAttribute("") E object, BindingResult result) {
if(service.cadastra(object))
return "yes";
else
return "not";
}
the parameter for the annotation ModelAtribute should be the name of an entity class from my project, which I have stored in the class member entity_class, but can't use because the annotation only accept constant values.
Also, I have this same problem with the annotation PreAutorize. I will use this annotation like that:
#PreAuthorize("hasPermission(#user, '<<permission_name>>')")
#RequestMapping(value="cadastra")
public ModelAndView cadastra() {
ModelAndView mav = new ModelAndView();
mav.setViewName("privado/"+this.entity_name+"/cadastra");
return mav;
}
and in my methods from my generic service too. The permission name follows this rule:
<<action_name>>_<<entity_name>>
and for each entity class I will have three permissions: cadastra (new_item), altera (change_item) and remove (remove_item).
Anyone can point a direction of how to solve this?
For the first question #ModelAttribute, what I've understood, you have several form calling the same Mapping method cadastra to verify if the informations are correct.
I think you can use a form parameter to tell which entity class it is, and use wrapper to wrap all of the entities that call this method.
create an Entity:
#Entity
public class Wrapper{
private Entity1 e1;
.....
in your jsp:
<form:form modelAttribute="wrapper" .../>
<form:hidden value="classname" .../>
<form:input path=e1.someProperty .../>
in your controller:
#RequestParam("classname") String classname;//used to get which entity it's
service.cadastra(wrapper.getEntityX())
for the second question I don't know much about #PreAuthorize annotation.

Spring 3 MVC: #ModelAttribute method + #Autowired not working

I am new to Spring and trying to learn #Autowired magic in SPring MVC. I was trying out a demo application using the #ModelAttribute method and #Autowired. Every time I am getting null which means #Autowired is not happening properly. Below is what I tried:
Controller
#Controller
public class ModelAttributeAutoWiredController {
#Autowired
private Employee empl;
public void setEmpl(Employee empl) {
this.empl = empl;
}
#RequestMapping(value="/home")
public ModelAndView returnhome(){
ModelAndView modelView = new ModelAndView("home");
System.out.println("Employee First Name: " + empl.getFirstName()); // NULL
return modelView;
}
#RequestMapping(value="/index")
public ModelAndView returnindex(){
ModelAndView modelView = new ModelAndView("index");
System.out.println("Employee Last Name: " + empl.getLastName()); // NULL
return modelView;
}
#ModelAttribute("empl")
public Employee populateEmployee(){
Employee empl = new Employee();
empl.setFirstName("XXX");
empl.setLastName("YYY");
return empl;
}
}
Employee
#Component
public class Employee {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
context xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<context:component-scan
base-package="com.pack" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Can someone please help me out in the above code and make me understand why #Autowired is not working?
You're confusing several Spring concepts here.
First, #Component is used for program components, generally objects that provide some service that other pieces of the program need. It is not intended for data objects such as your Employee class, and data objects that are runtime data (and not configuration objects) shouldn't be autowired, they should be passed in to the specific method calls that operate on them.
#ModelAttribute tells Spring that it should add whatever is being annotated to the MVC Model object so that it's available to the controller and view. This has nothing to do at all with #Autowired.
Here's what's happening in your code:
Your Employee class is annotated #Component, so Spring creates a singleton bean and registers it in the context. This bean never has its fields set, so they're null, but the bean itself exists, so it's wired into your controller's empl field. This is why you don't get a NullPointerException, which you would if the autowiring really weren't working.
Your #ModelAttribute is evaluated by Spring and added to the Model for each request. However, you never pass this model to any of your controllers, so they never see it.
Your controller methods create new, empty ModelAndView objects with nothing in them.
They then read the completely different, empty Employee object that was injected into empl and print out the null value on the fields (but don't throw NullPointerExceptions because the autowiring succeeded).
Please check this: http://www.mkyong.com/spring-mvc/spring-3-mvc-and-xml-example/
You'll need the <mvc:annotation-driven/> and also the <context:component-scan base-package="com.your.package" /> in your context.xml file.
Looks like this isn't possible and if you think about it, it doesn't make much sense to have a #ModelAttribute as an instance variable. Please read here to get a better understanding.
#Autowired is not intended for the domain objects like Employee.
In your case Spring creates an Employee object using the default constructor, so its lastname is null. Spring creates the Employee object because of #Component annotation on the Employee class, it has nothing to do with the #ModelAttribute.
The method annotated with #ModelAttributes instructs Spring to create another Employee object and inject it to the Model object that you don't have to create by yourself.
Just return "index" String from your "returnhome" method.
For debugging, you may add #ModelAttribute Employee e as an argument to your "returnhome" method.

list of string not displayed

I use spring 3. I'm trying to display a value of an object in a jsp.
public class UserForm implements Serializable {
private List<String> values;
private String date;
...
}
Here is my controller:
#Controller
#RequestMapping("/user.htm")
public class UserController {
#Autowired
private IUserService userService;
#RequestMapping(method = RequestMethod.GET)
public String userStat(Model model) {
UserForm stat = userService.populateStatistique();
stat.setDate("today");
model.addAttribute("statistiqueForm", stat);
return "userStat";
}
}
Here is my jsp:
<form:form commandName="userForm" name="userForm">
Date: <form:input path="date"></form:input>
<br/>
Expediteur:
<form:select path="values" items="${values}" />
<br/>
<input type="submit" value="create" />
</form:form>
In the jsp I can see the today value for the date field, but the listbox is empty.
any idea?
thanks
Well, use addAttribute("values", list) if you want it accessible in the jsp. You are currently not setting it and so it is empty.
If that list is contained in the statistiqueForm object, then use items="${statistiqueForm.values}".
You're correctly passing the form object to the jsp page. In that page you have a list box, which has a list of potential values and a list of selected values. The object form contains the list of selected values, but you should also setup the list with all potential values. In fact you reference ${values} in the jsp, but you don't pass it to the jsp. You should add this code to your controller:
model.addAttribute("values", potentialValueList);
I also suggest you to change the name of that list to avoid confusion, so it will be easy to understand the difference from selected values to potential values.

Equivalent Spring custom Collection property editor when using JSF

I would like to know how to use Converters in Java Server Faces similar to Spring collection property editor
Suppose the following model
public class Group {
private String name;
List<User> users = new ArrayList<User>();
// getter's and setter's
}
And equivalent form
<form ...>
<h1>Group form</h1>
<label for="name">Enter name</label>
<input type="text" name="name"/>
<label for="users">Select users</label>
<!--value attribute stores userId-->
<input type="checkbox" value="1" name="users"/> User 1
<input type="checkbox" value="2" name="users"/> User 2
<input type="checkbox" value="3" name="users"/> User 3
</form>
If i use Spring to bind users property in Group class, i call
binder.registerCustomEditor(List.class, new CustomCollectionEditor() {
protected Object convertElement(Object userId) {
return new User((Integer) userId);
}
});
How do i get the same effect when using Java Server Faces ?
regards,
For that you can implement javax.faces.convert.Converter. Its API is pretty self-explaining: write the getAsString() method accordingly that it returns the String representation of the Object, which can be under each the technical ID such as userId. Then, to get JSF set the right Object during apply request parameters phase, you need to implement getAsObject() that it returns the Object associated with the given String value.
Basically:
public class UserConverter implements Converter {
private UserDAO userDAO = SomeDAOManager.getUserDAO();
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((User) value).getId());
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return userDAO.find(Long.valueOf(value));
}
}
Register it in faces-config.xml as follows:
<converter>
<converter-for-class>com.example.model.User</converter-for-class>
<converter-class>com.example.converter.UserConverter</converter-class>
</converter>
That should be it. For more insights you may this or this article useful.

Categories

Resources