In my last article, “Understanding the Java Portlet Specification,” I introduced to the main concepts behind the Java Portlet Specification, JSR-168. This article will walk through the development of a simple portlet application to demonstrate the inner workings of a compliant portlet. The application will highlight only the more important areas of the specification. We will review terminology as it comes up. However, if you are new to the specification, I recommend you read the introductory article first.
Query Portlet
QueryPortlet is a simple portlet that displays the contents of a database query. Each user can modify the default query, saving their new query so it executes each time they begin a portal session.
As I mentioned in my previous article, there are several commercial and open-source vendors currently supporting the Java Portlet Specification. I have chosen to use Liferay for deploying my portlet. Liferay is an open-source portal that comes bundled with numerous portlet applications. I downloaded the Jboss-Tomcat binaries from Liferay’s download page and had it installed within minutes. Then, I followed the instructions here to hot deploy custom portlet applications into Liferay. To run QueryPortlet, you will also need a database and its JDBC driver. I used the MySQL database.
Viewing the QueryPortlet
Recall that each portlet application has two types of state managed by the portlet container: portlet mode and window state. The standard portlet modes are view, edit, and help (although additional ones may be added). The possible window states are minimized, normal, and maximized. Portlet mode determines which content a portlet will generate and window state determines just how much screen real estate the portlet is allowed.
Figure 1 shows our main portal window when we first log in. One of the portlets that appears is QueryPortlet; it presents data in tabular fashion and a title for that data. For each of QueryPortlet’s screens, I’ve added text in the upper right-hand corner to display the current mode and window state. As you can see, we are currently viewing QueryPortlet in view mode with a normal window state.
Figure 1. Main Portal Window
QueryPortlet, and all compliant portlets, define four lifecycle methods: init, render, processAction, and destroy. These methods are invoked by the portlet container as needed. On startup, the portlet container receives a request for QueryPortlet’s content from the portal application. Because the portlet has not been instantiated, the init lifecycle method is invoked. QueryPortlet.java extends GenericPortlet, a convenience class provided by the implementation that implements the render lifecycle method for you. Here is the init method in QueryPortlet.java:
/**Executed one time, upon portlet instantiation.*/public void init(PortletConfig config)throws PortletException { log.info("Executing QueryPortlet's init method."); super.init(config); viewUrl = config.getInitParameter("view_url"); editUrl = config.getInitParameter("edit_url"); helpUrl = config.getInitParameter("help_url");}
The init method passes in an instance of PortletConfig. PortletConfig provides access to initialization parameters in our descriptor file, portlet.xml . We assign values to instance variables that hold QueryPortlet’s URLs. Here are those values in portlet.xml:
<init-param> <name>view_url</name> <value>/templates/view.jsp</value></init-param><init-param> <name>edit_url</name> <value>/templates/edit.jsp</value></init-param><init-param> <name>help_url</name> <value>/templates/help.jsp</value></init-param>
After invoking init, the container calls the render lifecycle method. Render will call QueryPortlet’s doView method because we are in view mode at startup. QueryPortlet’s raison d’être is to display database data, so we need to execute a query. QueryPortlet retrieves a default title and query from PortletPreferences, an object that contains persisted user information associated with a portlet. PortletPreferences reads these default values from portlet.xml.
<portlet-preferences> <preference> <name>sql</name> <value>SELECT * FROM emp</value> </preference> <preference> <name>title</name> <value>Employees</value> </preference> <preferences-validator>QueryPreferencesValidator </preferences-validator> </portlet-preferences>
In QueryPortlet’s doView method, we obtain these default preferences:
PortletPreferences preferences = request.getPreferences();String sql = preferences.getValue("sql","BAD SQL");String title = preferences.getValue("title,"My Query");
The getValue method specifies the name of the preference and also a default value if it should be unable to obtain one otherwise.
We pass the SQL string to a JavaBean called QueryBean to perform the actual query:
PortletSession session = request.getPortletSession(true);if((session.getAttribute("queryColumns") == null) || (session.getAttribute("queryData") == null) ) { QueryBean qb = new QueryBean(); qb.executeQuery(sql); //Get headings/data and assign them to session attributes. String[] queryColumns = (String [])qb.getQueryColumns(); List queryData = (List)qb.getQueryData(); session.setAttribute("title",title,PortletSession.PORTLET_SCOPE); session.setAttribute("queryColumns",queryColumns, PortletSession.PORTLET_SCOPE); session.setAttribute("queryData",queryData, PortletSession.PORTLET_SCOPE);}
Similar to HttpSession, the PortletSession interface stores data about a user’s session. However, PortletSession defines two additional scopes for storing portlet-specific data within that user session. An application session scope stores data across all user portlets in the same session. In our example, we are using the second scope, portlet session scope, which stores information only accessible to the user’s QueryPortlet instance. The title and results of the query just performed are assigned to session attributes.
Finally, we use a PortletRequestDispatcher to forward to a JSP page that will generate markup. This is where our URL values read in the init method come in to play.
PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher(viewUrl);dispatcher.include(request,response);
Our view template, view.jsp, utilizes a portlet taglib. This taglib is used by portlets for building portlet links and performing the necessary includes of required classes. View.jsp then generates markup. This practice follows good MVC design. Our portlet methods receive requests, invoke necessary business logic contained in JavaBeans, and then forward to view pages to render a response.
Editing the QueryPortlet
Figure 2 shows QueryPortlet in edit mode. In Liferay, clicking the pencil icon sends a request to the container to bring a portlet up in edit mode. This results in the doEdit method being called on QueryPortlet. In edit mode, QueryPortlet reads the values of title and sql from PortletPreferences and sets RenderRequest attributes to display this information in an HTML form contained in the file edit.jsp. The first time we bring up the form, it displays our default preferences.
Figure 2. QueryPortlet in Edit Mode
A user can enter a new SQL query and title, and then submit the form. For this example, we will enter the query ‘SELECT emp_id ID, CONCAT(last_name,”,”,first_name) NAME, TITLE FROM emp, job WHERE emp.job_id = job.job_id order by title’ and a title of ‘My Employee Information with Job Title!’, and click submit. This action brings the processAction lifecycle method into play. Here is processAction:
/**Executed in response to action on portlet, like submitting a *form.*/public void processAction(ActionRequest request, ActionResponse response)throws PortletException, IOException { log.info("Executing QueryPortlet's processAction method."); if(request.getPortletMode().equals(PortletMode.EDIT)) { String errorMessage = null; boolean isValid = false; PortletPreferences preferences = request.getPreferences(); //Get our edit form's data... String sql = request.getParameter("sql"); String title = request.getParameter("title"); preferences.setValue("sql",sql); preferences.setValue("title",title); try{ //This will fire our PreferencesValidation validate method. preferences.store(); isValid = true; } catch(ValidatorException ve) { //Assign values from ActionResponse to RenderRequest for //redisplay in form. response.setRenderParameter("sql", request.getParameter("sql")); response.setRenderParameter("title", request.getParameter("title")); errorMessage = "An error occurred processing your SQL. " + "Please check your input and try again."; response.setRenderParameter("errorMessage",errorMessage); } if (isValid) { response.setPortletMode(PortletMode.VIEW); } }}
If mode equals ‘EDIT’, processAction assigns the form’s values to PortletPreferences. However, before we move on, we will want to validate the query. The portlet specification defines a convenient way to validate a user’s portlet preferences by implementing the PreferencesValidator interface. We designated a validator class in portlet.xml that is to fire when portletAction invokes the store() method on PortletPrefernces.
<portlet-preferences> ... ... <preferences-validator>QueryPreferencesValidator </preferences-validator></portlet-preferences>
QueryPortlet is simplistic and does not pretend to be a robust application. QueryPreferencesValidator.java simply reads the value of sql in PortletPreferences and validates the query by invoking the executeQuery method on QueryBean.
Entering the invalid query ‘SELECTX * FROM emp’ in the form and pressing Enter will generate an Exception. In this case, we log the error and throw a new ValidatorException with a message for users to correct their query and try again:
try { QueryBean qb = new QueryBean(); qb.executeQuery(sql);}catch(Exception e) { log.warn("Exception occured executing QueryBean executeQuery in " + "QueryPreferencesValidator. sql : " + sql); errorsSet.add(sql); throw new ValidatorException(e.toString(),errorsSet);}
QueryPortlet’s portletAction receives the ValidatorException in this case. We want the user to be able to see the invalid form data they’ve just submitted, not what is in PortletPreferences (which has been reset to its valid state). We do this by calling ActionResponse’s setRenderParameter with the user’s initial input values:
catch(ValidatorException ve) { //Assign values from ActionResponse to RenderRequest for //redisplay in form. response.setRenderParameter("sql",request.getParameter("sql")); response.setRenderParameter("title",request.getParameter("title")); errorMessage = "An error occurred processing your SQL. " + "Please check your input and try again."; response.setRenderParameter("errorMessage",errorMessage);}
Now, if the SQL is corrected and resubmitted, validation will succeed and processAction will change the portlet mode form edit back to view:
try{ //This will fire our PreferencesValidation validate method. preferences.store(); isValid = true;}catch(ValidatorException ve) { ... ...}if (isValid) { response.setPortletMode(PortletMode.VIEW);}
The container invokes the render method once more, and because QueryPortlet is in view mode, the doView method executes. Figure 3 shows the output.
Figure 3. QueryPortlet in View Mode
You can download and install the QueryPortlet application, and then follow the execution path of the portlet by viewing the application’s log file (I used Apache Commons Logging package for this which uses J2SDK 1.4’s logger).
Conclusion
With the growing popularity of enterprise portals, understanding the Java Portlet Specification will become increasingly important to J2EE developers. There are a number of open-source portals available that will allow you to experiment with portlet development and I encourage to learn more about this important technology.
About the Author
Michael Klaene is a Senior Consultant with Sogeti LLC. He has spent over 7 years in IT, specializing in J2EE and Oracle analysis and development.