Liferay diaries: Multiple spring form controllers in a portlet

Now there comes a moment in the life of every portlet author, be it a seasoned veteran (like jstam) or a fresh starter like myself when you need to do implement something out of the ordinary for which portlets were not intended.

Last week we had to scetch up an all-in-one portlet that could display several different forms, perform queries and display the results. Since these forms are complicated, we decided to use Spring’s FormControllers to handle validation, error messages and of course binding of HTTP parameters to our form backing beans.

The problem

As it turns out, this is not trivial as there are some technical hickhups to silence first.
Since the same portlet is to process all GET and POST requests, we can’t differentiate the views by different URL(at least not the path part, i.e. /myapp/portlet1, /myapp/portlet2 etc).
Instinctively I’m looking for other ways to differentiate portlet views and I arrive at view modes. Unfortunatelly it turns out that custom view modes are optional and not implemented [1]

The solution

What we need is a clever portlet handler that can read the requested URL and decide which form controller to invoke.
My solution looks like this:

public class PortletHandlerMapping extends AbstractMapBasedHandlerMapping implements InitializingBean{

private Map mappings = new HashMap();

public void setMappings(Map mappings){
this.mappings = mappings;
}

@Override
protected Object getLookupKey(PortletRequest request) throws Exception {
Object key = request.getParameter("jspPage");
return key;
}

@Override
public void afterPropertiesSet() throws Exception {
for (String key:mappings.keySet())
registerHandler(key, mappings.get(key));
}

}

The contract of this handler assumes that there is a request parameter named “jspPage” which contains the (short, thus without the package) class name of the FormController to invoke.

A controller would look like this:

@Controller
@RequestMapping("SearchDeliveriesController")
public class SearchDeliveriesController {

@ActionMapping
public void findDeliveries(SearchDeliveryQuery query,
BindingResult result, @CookieValue("JSESSIONID") String jsessionid,
PortletSession session, ModelMap modelMap) {
if (!result.hasErrors()) {
modelMap.put("msg", "Ok, submit completed");
List rs = deliveriesDao.search(query);
modelMap.put("result", rs);
} else{
modelMap.put("msg", "Has errors");
}
// we need that, otherwise the next form will forget current values
modelMap.put("searchDeliveryQuery", query);
}

@ResourceMapping
public String viewAsText(ResourceResponse response) {
response.setContentType("text/plain");
return "searchDeliveriesResults";
}

@RenderMapping
public String view() {
return "searchDeliveries";
}

// the model name must match the class name, otherwise error handling won't work
@ModelAttribute("searchDeliveryQuery")
public SearchDeliveryQuery loadModel() {
SearchDeliveryQuery query = new SearchDeliveryQuery();
return query;
}
}

There is one last thing: because of the two phase request lifecycle for portlets (see my previous post blog.open.gr/home/-/blogs/liferay-diaries:-help!-my-form-post-parameters-are-empty!) we need a mechanism that preserves HTTP parameters from the action phase to the render phase:

public class ParameterForwardingInterceptor implements HandlerInterceptor{

@Override
public boolean preHandleAction(ActionRequest request,
ActionResponse response, Object handler) throws Exception {
response.getRenderParameterMap().putAll(request.getParameterMap());
return true;
}
...

}

Since we’ve not implemented any clever context scanning, this approach will not discover controllers on its own (despite them being annotated), thus we have to declare them in the portlet.

 <bean id="dashboardController" class="gr.open.acs.customersarea.web.DashboardController"/>
<bean id="searchDeliveriesController" class="gr.open.acs.customersarea.web.SearchDeliveriesController"/>

<bean id="portletModeHandlerMapping"
class="gr.open.acs.customersarea.web.PortletHandlerMapping">
<property name="defaultHandler" ref="dashboardController"/>
<property name="interceptors">
<bean class="gr.open.acs.customersarea.web.ParameterForwardingInterceptor"/>
</property>
<property name="mappings">
<map>
<entry key="view" value-ref="dashboardController"/>
<entry key="DashboardController" value-ref="dashboardController"/>
<entry key="SearchDeliveriesController" value-ref="searchDeliveriesController"/>
</map>
</property>
</bean>


<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>

[1] Discussion on Stackoverflow
http://stackoverflow.com/questions/5434349/liferay-6-0-5-and-spring-mvc-3-question

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s