Error Handling with Spring Portlet MVC

>> Thursday, October 27, 2011


In Liferay Portal, when an exception is thrown in a portlet, the default user experience is to view a message that the "Portlet is no longer available" in lieu and place of the portlet content. This can be acceptable is some circumstances but let's face it, when a user reports a problem there is not much contextual info he can share with us.

I recently worked on a major portal application with a lot of backoffice (control panel) portlets and error reports were not easy to diagnose since we had to go digging for the event trace in the log files of the staging or production servers. Surely there was a better way to manage this. As it happens, all our portlets use the Spring Portlet MVC framework and with very little effort we can provide the user or tester with more info on what caused the error and more importantly, the user can include (read: paste) the stack trace in his error report. So here is how to do it for anybody with a similar need.

First, in the Spring portlet context file, add the following bean definition:

      <bean  
           class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
 <!--           <property name="exceptionMappings"> -->  
 <!--                <map> -->  
 <!--                     <entry key="DataAccessException" value="data-error" /> -->  
 <!--                     <entry key="com.stuff.MyAppRuntimeException" value="app-unchecked-error" /> -->  
 <!--                     <entry key="com.stuff.MyAppCheckedException" value="app-checked-error" /> -->  
 <!--                </map> -->  
 <!--           </property> -->  
           <property name="defaultErrorView" value="general-error" />  
      </bean>  

This tells Spring what view to display when a controller receives an exception. The commented code can be used as an example to follow if we want to display different views based on the type of exceptions. We just use a generic error view by setting the "defaultErrorView". The later is a JSP file called general-error.jsp that we include in our plugin and it is coded as shown below.

 <%@page import="com.liferay.portal.util.PortalUtil"%>  
 <%@page import="com.liferay.portal.kernel.language.LanguageUtil"%>  
 <%@page import="java.io.PrintWriter"%>  
 <%@ include file="/WEB-INF/jsp/include.jsp" %>  
 <portlet:defineObjects />  
 <%  
 String portletTitle = PortalUtil.getPortletTitle(renderResponse);  
 if (portletTitle == null) {  
      portletTitle = LanguageUtil.get(pageContext, "portlet");  
 }  
 Throwable t = (Throwable) request.getAttribute("exception");  
 %>  
 <div class="portlet-msg-error">  
      <%= LanguageUtil.format(pageContext, "is-temporarily-unavailable", portletTitle, false) %>  
 </div>  
 <p>  
 <liferay-ui:message key="error-cause" /> <%= t.getMessage() %>  
 </p>  
 <liferay-ui:toggle-area id="toggle_area" showMessage='<%=LanguageUtil.get(pageContext, "Show-stack-trace")%>'   
      hideMessage='<%=LanguageUtil.get(pageContext, "Hide-stack-trace")%>'   
      hideImage="true" defaultShowContent="false" >  
      <div class="toggle_area"><br/>  
           <textarea style="width: 700px; height: 200px;" readonly="yes" wrap="off">  
 <%   
 if (t != null) {  
      t.printStackTrace(new PrintWriter(out));   
 }  
 %>  
           </textarea>  
      </div>  
 </liferay-ui:toggle-area>  
 <br/>  
 <hr/>  

What this does is very simple and is shown in the image below. When an error occurs, the usual message is displayed but, in addition, we display the specific error message and the user can elect to display the stack trace in an expandable textarea. By default, the textarea is collapsed so as to not intimidate the user...


There is one final thing that needs to be done to attach all the strings. We need to add an annotated method in our PortletMVC controller so that Spring knows what to do when an exception occurs. Here is the (extremeley complex) method:


   @ExceptionHandler(Exception.class)  
   public String handleException (Exception ex, RenderRequest request) {  
    request.setAttribute("exception", ex);  
    return "general-error";  
   }  


Naturally, if you have several controllers in your Liferay plugin, the idea is to put this method in an abstract superclass and have all your controllers that use this error view extend the superclass.

To finalize everything, you will want to add the following to your resource bundle (*.properties file) and localize theses labels as needed.


error-cause=Cause:
Show-stack-trace=Show stack trace
Hide-stack-trace=Hide stack trace


Have fun with your Liferay / Spring portlets (and make sure your users/testers send you those stack traces...).


Read more...

  © Blogger template Webnolia by Ourblogtemplates.com 2009

Back to TOP