Spring Web Flow - Conversion of nested / rich / complex model objects - spring-webflow

How do we bind complex model objects in Spring Web Flow?
Take the following example:
public class User {
int id;
String name;
Address address;
}
public class Address {
int id;
String street;
String city;
}
In the view, I have to update the address for this user from address Id. The scenario is where user searches for different addresses and links one address to the user. Since the search result is address ID, how do we convert the address Id to address object?
The current view that I have is -
<form:form method="POST" modelAttribute="user" action="${flowExecutionUrl}">
<form:inputpath="address.id" />
</form:form>
The web flow is the following:
<view-state id="updateAddress" model="flowScope.user">
<binder>
<binding property="address" />
</binder>
<transition on="addressSubmitted" to="addNextInfo"></transition>
<transition on="cancel" to="cancelled" bind="false" />
</view-state>
So, two questions:
How do we convert the address id to address object inside the user object automatically? I was planning to use Spring converter to do this, but I do not know whether it is for this purpose or is there a better way?
Are the other parts in the application that I am showing correct?

I think using a custom converter is exactly right. You'd probably want to extend the existing StringToObject converter. I'm guessing some classes here but the converter may look something like this:
public class StringToAddress extends StringToObject {
private final AddressService addressService;
public StringToAddress(AddressService addressService) {
super(Address.class);
this.addressService = addressService;
}
#Override
protected Object toObject(String string, Class targetClass) throws Exception {
return addressService.findById(string);
}
#Override
protected String toString(Object object) throws Exception {
Address address = (Address) object;
return String.valueOf(address.getId());
}
}
And then you'll need to configure the conversion service:
#Component("conversionService")
public class ApplicationConversionService extends DefaultConversionService {
#Autowired
private AddressService addressService;
#Override
protected void addDefaultConverters() {
super.addDefaultConverters();
// registers a default converter for the Address type
addConverter(new StringToAddress(addressService));
}
}
That will then automatically kick-in whenever Spring attempts to bind the id to the Address field (and will lookup and bind the real Address object).
Keith Donald goes through an example custom converter setup in his post here which is worth referencing.

Related

Is there a way to customize the ObjectMapper used by Spring MVC without returning String?

I have a graph of objects that I'd like to return different views of. I don't want to use Jackson's #JsonViews to implement this. Right now, I use Jackson MixIn classes to configure which fields are shown. However, all my rest methods return a String rather than a type like BusinessCategory or Collection< BusinessCategory >. I can't figure out a way to dynamically configure the Jackson serializer based on what view I'd like of the data. Is there any feature built into Spring to configure which Jackson serializer to use on a per-function basis? I've found posts mentioning storing which fields you want in serialized in thread-local and having a filter send them and another post filtering based on Spring #Role, but nothing addressing choosing a serializer (or MixIn) on a per-function basis. Any ideas?
The key to me thinking a proposed solution is good is if the return type is an object, not String.
Here are the objects in my graph.
public class BusinessCategory implements Comparable<BusinessCategory> {
private String name;
private Set<BusinessCategory> parentCategories = new TreeSet<>();
private Set<BusinessCategory> childCategories = new TreeSet<>();
// getters, setters, compareTo, et cetera
}
I am sending these across the wire from a Spring MVC controller as JSON like so:
#RestController
#RequestMapping("/business")
public class BusinessMVC {
private Jackson2ObjectMapperBuilder mapperBuilder;
private ObjectMapper parentOnlyMapper;
#Autowired
public BusinessMVCfinal(Jackson2ObjectMapperBuilder mapperBuilder) {
this.mapperBuilder = mapperBuilder;
this.parentOnlyMapper = mapperBuilder.build();
parentOnlyMapper.registerModule(new BusinessCategoryParentsOnlyMapperModule());
}
#RequestMapping(value="/business_category/parents/{categoryName}")
#ResponseBody
public String getParentCategories(#PathVariable String categoryName) throws JsonProcessingException {
return parentOnlyMapper.writeValueAsString(
BusinessCategory.businessCategoryForName(categoryName));
}
}
I have configure the serialization in a MixIn which is in turn added to the ObjectMapper using a module.
public interface BusinessCategoryParentsOnlyMixIn {
#JsonProperty("name") String getName();
#JsonProperty("parentCategories") Set<BusinessCategory> getParentCategories();
#JsonIgnore Set<BusinessCategory> getChildCategories();
}
public class BusinessCategoryParentsOnlyMapperModule extends SimpleModule {
public BusinessCategoryParentsOnlyMapperModule() {
super("BusinessCategoryParentsOnlyMapperModule",
new Version(1, 0, 0, "SNAPSHOT", "", ""));
}
public void setupModule(SetupContext context) {
context.setMixInAnnotations(
BusinessCategory.class,
BusinessCategoryParentsOnlyMixIn.class);
}
}
My current solution works, it just doesn't feel very clean.
"categories" : [ {
"name" : "Personal Driver",
"parentCategories" : [ {
"name" : "Transportation",
"parentCategories" : [ ]
} ]
}
Oh yes, I'm using:
spring-boot 1.2.7
spring-framework: 4.1.8
jackson 2.6.3
Others listed here: http://docs.spring.io/spring-boot/docs/1.2.7.RELEASE/reference/html/appendix-dependency-versions.html
In the end, the only process that met my needs was to create a set of view objects which exposed only the fields I wanted to expose. In the grand scheme of things, it only added a small amount of seemingly unnecessary code to the project and made the flow of data easier to understand.

Storing a value globally across all pages

This is an ASP.NET Forms project. When user enters his/her user name and password (in the Login page) I want to save the user name such that it can be retrieved in the code of any page of the project. I know that I can do it via a session variable.
But is it possible to create a Static Public class with Get Set and store the value there and retrieve it using this class?
If you are using master page create a hidden field in that store that information in that hidden value so you can access in any page where you are using that master page.
Static classes are shared across instances/sessions in your app, which means you could end up with something akin to race conditions; for instance, a request from User_A could read values that were set in your static class by User_B. (see this SO answer)
Shooting from the hip, it might be easier to write a wrapper/abstraction class for your users' info that makes accessing their details easier. Something like:
public class UserDetails{
public string Name;
public string Age;
public string Gender;
public UserDetails(HttpContext context){
this.Name = context.User.Identity.Name;
this.Age = ...;
this.Gender = ...;
// Alternatively, you could perform your own data access to
// get/set these details. It depends on how you're storing your
// users' info.
}
}
Then in your code behind...
UserDetails userDetails = new UserDetails(context.Current);
Response.Write(userDetails.Name); // user's name
Response.Write(userDetails.Age); // user's age
...

How to convert String to List object in spring MVC

I am trying Spring MVC example. I have a testBean which has List type variable like
private List<HashMap<String, String>> books;
In my controller I have
#RequestMapping(value = "/booksList", method = RequestMethod.POST)
public String displayBooks(#ModelAttribute TestBean testBean, Model model, HttpSession session) {
// some code here
}
In my jsp page I have
<form:form action="booksList.html" method="post" modelAttribute="testBean">
<form:hidden path="books" />
<input type="submit" value="submit">
</form:form>
When I submit this form I am getting this error
Cannot convert value of type [java.lang.String] to required type [java.util.HashMap] for property 'books[0]'.
How can I solve this error ? please help
Updated:
I have seen that I can do this way also.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "testBean", new CustomCollectionEditor(List.class) {
#Override
protected Object convertElement(Object element) {
TestBean testBean = new TestBean();
if (element != null) {
List<HashMap<String, String>> id = (List<HashMap<String, String>>) element;
testBean.setFilters(id);
}
return testBean;
}
});
}
But I don't understand the above method fully there may be some mistakes in what I have written. And I don't know how and from where I can call convertElement(Object element) method. i do understand initBinder(WebDataBinder binder) will call jsut before my controller method public String displayBooks(...). Even I don't know if its the right way of doing this.
As you have it above, you're trying to represent an extremely complex type (a List of Maps) with a single text field. Your first step should be working out how you want to display that information in a web form. Then try using "canned data" (e.g. a TestBean that you've loaded up with fake books) and see if your form looks how you expect it to.
You might find it easier if you change the way your books collection is stored. Having such a complex type as part of a "bean" while allowed, is probably not recommended. Consider using a List<BookBean> where a BookBean holds a collection of BookDetailBean, and a BookDetailBean holds that String-to-String relationship that you previously had in your Map.

Send object to VIewModel with mvvm-light

I'm pretty new to MVVM light world, and after searches I can't find what I want to do.
My WP7 application contains a pivot, each pivot item content is View1 and viewmodel is VM1.
When loading my application, I'd like to create every pivot item with the same view and view model but with different parameter.
example :
PivotItem 1 -> send param "car" to the view model
PivotItem 2 -> send param "truck" to the view model, etc.
Google told me to use messaging but if I send 2 messages from my MainViewModel, both PivotItem1 and PivotItem2 ViewModel will receive these messages.
Am I wrong with this approach ?
Is there another solution to succeed ?
Thank you in advance for your answer.
PS : be indulgent, english is not my native language, don't hesitate to ask for further information.
Regards,
Aymeric Lagier
To seperate the messages use the second constructor signature whereby you can pass a token. This token can be anything but I generally use an enum to store all my message types within the system.
Create a static class in a common library and reference this in all projects where you need to send or receive messages.
The following code hopefully shows this approach, notice I am sending a string as a value within the message but this can be anything, even a complex object such as one of your business objects.
namespace MyProject.Common
{
public static class AppMessages
{
enum MessageTypes
{
ViewmodelA,
ViewmodelB
}
public static class ViewModelAUpdate
{
public static void Send(string value)
{
Messenger.Default.Send(value, MessageTypes.ViewmodelA);
}
public static void Register(object recipient, Action<string> action)
{
Messenger.Default.Register(recipient, MessageTypes.ViewmodelA, action);
}
}
public static class ViewModelBUpdate
{
public static void Send(string value)
{
Messenger.Default.Send(value, MessageTypes.ViewmodelB);
}
public static void Register(object recipient, Action<string> action)
{
Messenger.Default.Register(recipient, MessageTypes.ViewmodelB, action);
}
}
}
}
How about using a method to set the message you want to receive. (this could be done as a parameter in the constructor or a property as well)
public void RegisterForAppMessage(AppMessages.MessageTypes messageType)
{
switch (messageType)
{
case AppMessages.MessageTypes.PivotViewItem1Message:
AppMessages.PivotViewItem1Message.Register(this,DoSomethingWhenIRecievePivotViewItem1Messages)
break;
case AppMessages.MessageTypes.PivotViewItem2Message:
AppMessages.PivotViewItem2Message.Register(this,DoSomethingWhenIRecievePivotViewItem2Messages)
break;
}
}
private void DoSomethingWhenIRecievePivotViewItem2Messages(string obj)
{
// TODO: Implement this method
throw new NotImplementedException();
}
private void DoSomethingWhenIRecievePivotViewItem1Messages(string obj)
{
// TODO: Implement this method
throw new NotImplementedException();
}
Messaging sounds a bit heavy for this purpose. Could you simply inject a parameter into your ViewModel. If you already have MVVMLight you also have support for SimpleIOC. Maybe let the view locate its ViewModel when the view is resolved and there decide which parameter to use on the view model?
You can see an example of it here

Spring 3 MVC: Issue binding to list form fields on submit

Let me introduce my issue by providing some of the code in question.
First my form object:
public class OrgChartForm {
List<OrgChartFormElement> orgChartFormElements;
public OrgChartForm() {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
}
private OrgChartFormElement createOrgChartFormElementFromMprsStructureYear(MprsStructureYear structureYear){
OrgChartFormElement element = new OrgChartFormElement();
element.set.... // populate element based on attribute values from structureYear param
return element;
}
public void createOrgChartFormElements(List<MprsStructureYear> structureYears) {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
for(MprsStructureYear structureYear:structureYears){
orgChartFormElements.add(createOrgChartFormElementFromMprsStructureYear(structureYear));
}
}
// expected getters and setters
}
The form contains a simple list of OrgChartFormElements
public class OrgChartFormElement {
private boolean selected;
private String elementLabel;
private Long id;
//default constructor, getters and setters
}
I am using context:component-scan and mvc:annotation-driven, so my controller looks like:
#Controller
public class OrganisationStatusController{
#Autowired(required=true)
// dependencies here
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.GET)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId, Model model) throws Exception {
List<MprsStructureYear> orgStructuure = getOrganisationService().getOrganisationStructureForFinyear(finyearId);
OrgChartForm orgChartForm = new OrgChartForm();
orgChartForm.createOrgChartFormElements(orgStructuure);
model.addAttribute("orgChartForm", orgChartForm);
return "finyear/organisationchart/view";
}
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.POST)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId,#ModelAttribute("orgChartForm") OrgChartForm orgChartForm, BindingResult result, Model model) throws Exception {
System.out.println("Found model attribute: " + model.containsAttribute("orgChartForm"));
List<OrgChartFormElement> elements = orgChartForm.getOrgChartFormElements();
System.out.println(elements);
return "redirect:/spring/finyear/" + finyearId + "/organisationstatus";
}
// expected getters and setters
}
The issue is with the POST handler. I realise that it isn't doing much now, but once I get it to work, I will be persisting the submitted values.
At the moment, the output i see from the two sysout statements are:
Found model attribute: true
[]
Here is my JSP snippet:
<sf:form modelAttribute="orgChartForm" method="post">
<c:forEach items="${orgChartForm.orgChartFormElements}" var="org" varStatus="status">
<sf:hidden id="${org.id}field" path="orgChartFormElements[${status.index}].id"/>
<sf:input id="${org.id}hidden" path="orgChartFormElements[${status.index}].selected"/>
<c:out value="${org.elementLabel}"/>(<c:out value="${org.id}"/>) - <c:out value="${status.index}"/>
</c:forEach>
<input type="submit" value="Submit" />
</sf:form>
When i make the GET request, the JSP renders, and i see my list of text input fields, with the expected values, which tells me that im using the spring-form tags properly. However, when i submit, the form backing object declared as a parameter (orgChartForm) in the POST handler method is initialised, but everything is null/default initialised. I don't know where the submitted data went! It seems that springMVC looses it, and simply constucts a new object.
I have used this pattern extensively in this application without a glitch. It just wont work here. I realise this is a special case in my application where the form field is not atomic but a list, However its really confusing me that the data binds in the GET request, but not on the POST.
Thanks in advance for any pointers!
I think the problem is that you are trying to bind an arbitrary number of form fields to an ArrayList, which is a list that has a predetermined size.
Spring has something called an AutoPopulatingList that is custom designed for this purpose. Please have a look at this link for more info on how to use it: http://blog.richardadamdean.com/?p=12
I think you will need to write PropertyEditorSupport for your class. Following is the example for your reference.
public class SampleEditor extends PropertyEditorSupport {
private final SampleService sampleService;
public SampleEditor(SampleService sampleService, Class collectionType) {
super(collectionType);
this.sampleService = sampleService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
Object obj = getValue();
List list = (List) obj;
for (String str : text.split(",")) {
list.add(sampleService.get(Long.valueOf(str)));
}
}
#Override
public String getAsText() {
return super.getAsText();
}
}
In controller, you should bind it using #InitBinder as follows:
#InitBinder
protected void initBinder(HttpServletRequest request, WebDataBinder binder) {
binder.registerCustomEditor(List.class, "list", new SampleEditor(this.sampleService, List.class));
}
Hope this will solve your problem.

Resources