The Model-View-Controller (MVC) pattern is one of the most commonly used patterns for designing complex web applications. Several MVC frameworks (e.g., Struts) exist for building servlet-based web applications, but these frameworks do not support portlet development. This article provides an overview of MVCPortlet, a framework that addresses this need.
MVCPortlet is a framework for developing JSR-168-compliant portlets using a pattern similar to MVC. The framework provides a
ControllerPortlet that receives all
render() requests. A request is delegated to a processor that implements the business logic for a specific request type. The processors may manipulate the model by directly accessing a relational database or via Java/EJBs. The view is generated by forwarding the render requests to JSPs.
Features offered by MVCPortlet framework at the time of writing this article include:
- Support for the JSR 168 standard
- Server- and client-side input validation
- Extensible permissioning module for action/render type level access control
- No coding required for simple database-driven portlets
- Device-specific display generation (XHTML/WAP/HTML)
- Tag library for navigation, form elements
- Support for seamless file uploads
Please note that this article assumes familiarity with concepts related to portlets and the JSR 168 standard. Uninitiated readers are strongly recommended to read at least the JSR 168 standards specification before going further.
The MVCPortlet Framework and the MVC Pattern
The main idea behind the MVC design pattern is the separation of
- The system state (Model)
- The mechanism used to pass on model manipulation requests (Controller)
- The presentation (Views)
Decomposing the system in this fashion helps in maintaining system integrity while providing multiple ways of viewing and editing the system.
The Model in an MVC-based system maintains system state and provides operations to manipulate that state. Model state is commonly stored in a relational database. Two-tier systems use database's SQL interface to manipulate the state. Most of the newer implementations create an object-oriented abstraction layer that represents the model.
The current release of MVCPortlet provides
RenderProcessorimplementations that perform the database operations used in a typical data-driven application. In the future we will add the ability to work with Java Beans and EJBs. In any case, larger applications should have their own business objects that implement the model independent of any external framework. In such cases, the processor classes would simply delegate the real work to these business objects.
The View portion of a MVCPortlet-based application is commonly constructed using JSP technology. Technically, you can also include output generated by plain HTML files or other servlets. MVCPortlet provides a number of JSP tags geared towards use of JSPs in portlet-based applications. MVCPortlet tags facilitate internationalization, form input element generation, creation of navigation links, etc. Developers can also leverage all of the features available in standard JSP technologies, but one has to operate within the constraints and guidelines imposed by the portlet API specification. For example, request parameters must not be obtained from the
request object available in a JSP page. The parameters must be accessed from the
RenderRequest object that is available either via the request attribute
javax.portlet.renderRequest or by using a
defineObjects tag that defines a
renderRequest system variable.
The Controller portion of MVCPortlet, the
ControllerPortlet class, handles all incoming
render() requests. In both methods, the controller selects a processor based on a request parameter named
request_type. The association between request type and a processor is defined in theportlet-config.xml file, typically located at the top level in the portlet base directory. The
render() method delegates incoming requests to objects implementing
processAction() method delegates requests to objects implementing the
com.nabhinc.portlet.mvcportlet.ActionProcessorinterface. Processors return a logical name indicating the result of their execution. The configuration file maps the logical name to the JSP file responsible for generating the display for the current request type.
ControllerPortlet includes content generated by this JSP file to complete the request processing.
In most cases, developing a portlet application using MVCPortlet simply involves configuring and installing an instance of
ControllerPortlet. This section presents an overview of the lifecycle of such a portlet application, including initialization, the handling of
processAction() methods, and portlet destruction.
ControllerPortlet initialization mainly consists of reading portlet configuration files and constructing instances of supporting classes needed for portlet operation.
ControllerPortlet expects at least one parameter,
configFile, in its portlet configuration, defined in the portlet.xml file. The value of this parameter must be the context-relative path for the portlet configuration file. In addition, you can also specify the
messageFileparameter. This parameter specifies the location of the file that defines internationalized message strings. If this path starts with a "/", then it is assumed to be relative to the web application root directory. Otherwise, it is assumed to be in the portlet base directory specified in the portlet configuration file.
Render Request Processing
The portlet container invokes the
render() method on
ControllerPortlet whenever a page containing the portlet is loaded. Figure 1 shows the high-level steps in generating markup during this method. When
ControllerPortlet receives a render request, it first looks at the request type, i.e., the value of the
request_type parameter. It checks if the current caller has the necessary permission to access the current request type. The MVCPortlet framework offers a very expressive permissioning mechanism that allows access control based on user IDs, client IP, roles, relationships, etc. A
PortletSecurityException is thrown if the client does not have access for the current request type.
If there is a
RenderProcessor associated with this request type,
ControllerPortlet invokes the
process method on the
process method can choose to return a key that specifies the next step in business logic execution. The key can either map to an include path or to another
RenderProcessor. The mapping is specified in theportlet-config.xml file for that portlet. This feature can be used to quickly assemble business functionality from a set of reusable
If the key returned by a
RenderProcessor maps to an include path, we move on to the view generation step. Portlet developers can specify a client-specific mapping for such a key. At the time of this writing, the framework allows the generation of HTML, WML, and XHTML Mobile Profile displays. Typically, these will be JSP files that generate the requested markup based on request attributes and parameters set by the
RenderProcessorobjects for the current request type.
Action Request Processing
Execution of the
processAction() method (depicted in Figure 2) is similar to the
render requests. It begins with a permission check for the current request type, identified by the
request_type request parameter. If there is a form associated with the current request type, the form fields are validated. A series of
ActionProcessorobjects can be chained together to execute the business logic in a manner similar to the chaining of
RenderRequestobjects in the
render() method. After the last
ActionProcessor finishes its request handling, the processor result key is mapped to either a request redirect or a render type. In case of a redirect, the portlet container will redirect the current request to the specified URL. In case of a render type, the render parameter
request_type is set to that value. This value will specify the request type in the subsequent render requests on the portlet showing the results of the current action.
The portlet container calls the
destroy() method on a
ControllerPortlet instance at the end of its lifecycle. This method in turn calls the
destroy()method on all registered
RenderProcessor objects. This method can be used to release any resources that are acquired by processor objects.
Portlet Development Example
To illustrate the portlet development process, we consider the steps involved in creating the FAQ portlet included in MVCPortlet distribution. Figure 3 shows the home page for the FAQ portlet in
VIEW mode. The home page shows a list of FAQ categories. When the user clicks on a category, questions and answers belonging to the category are displayed, as shown in Figure 4.
Defining Render Types
The first step in portlet development is to figure out the portlet modes and render types to be supported by the portlet. In this case, our portlet needs to support a
VIEW mode and an
ADMIN mode. The portlet will display the FAQ information as shown in Figures 3 and 4 in the
ADMIN mode will allow the manipulation of FAQ categories and questions. Note that the admin screens are not included in this article to keep it concise. The view mode supports the following render types:
ViewIndex: Portlet home page that displays a list of FAQ categories.
ViewCategory: Page displaying questions and answers belonging to the specified category.
ADMINmode needs to support the following render types:
AdminIndex: Home page for
ADMINmode. Provides links for creation, deletion, and editing categories.
CreateCategory: Displays a form for creating a new FAQ category.
EditCategory: Displays a form for editing an existing category.
EditQuestions: Main page for manipulating questions belonging to a category.
CreateQuestion: Displays a form for creating a FAQ question.
EditQuestion: Displays a form for editing a FAQ question.
The corresponding configuration in the portlet-config.xmlfile is shown in Listing 1. Notice that the JSP file used for generating portlet markup is specified by the
include-path element. The directory path for the JSP file is specified by the
base-dir element (not shown in the listing). In addition, there is a navigation node assigned to each render type, as specified in the
nav element. Discussion of navigation implementation is out of scope for this article, but interested readers can look up the MVCPortlet User Guide for more details.
<render-types> <render-type name="ViewIndex"> <nav label="faq.view_index_header" reset="true" /> <include-path name="default" path="ViewIndex.jsp" /> </render-type> <render-type name="ViewCategory"> <nav label="faq.view_category_header" idParam="cat_id" nameParam="cat_title" /> <include-path path="ViewCategory.jsp" /> </render-type> <render-type name="EditCategory" portletMode="admin"> <nav label="faq.edit_category_header" idParam="cat_id" nameParam="cat_title" /> <include-path path="EditCategory.jsp" /> </render-type> <render-type name="CreateCategory" portletMode="admin" preCondition="admin"> <nav label="faq.create_category_header" /> <include-path path="CreateCategory.jsp" /> </render-type> <render-type name="AdminIndex" portletMode="admin" preCondition="admin" > <nav label="faq.admin_index_header" reset="true" /> <include-path name="default" path="AdminIndex.jsp" /> </render-type> <render-type name="EditQuestions" portletMode="admin" preCondition="admin" > <nav label="faq.edit_questions_header" idParam="cat_id" nameParam="cat_title" /> <include-path path="EditQuestions.jsp" /> </render-type> <render-type name="EditQuestion" portletMode="admin" preCondition="admin" > <nav label="faq.edit_question_header" idParam="q_id" nameParam="q_title" /> <include-path path="EditQuestion.jsp" /> </render-type> <render-type name="CreateQuestion" portletMode="admin" preCondition="admin" > <nav label="faq.create_question_header" /> <include-path path="CreateQuestion.jsp" /> </render-type> </render-types>
Listing 1. Configuration of FAQ portlet render types
Defining Action Types
Category and question data used by the FAQ portlet is stored in a relational database, and implementing admin functionality requires a number of action types that create, delete, and modify database records. These action types are passed to
processAction() method via the request parameter
request_type. We need the following action types in the FAQ portlet:
CreateCategory: Create a new category record.
UpdateCategory: Update category information.
DeleteCategory: Delete a category record.
EditCategory: Used to populate the render parameters for the subsequent
CreateQuestion: Create a new question record.
UpdateQuestion: Update question information.
DeleteQuestion: Delete a question record.
EditQuestion: Used to populate the render parameters for the subsequent
A fragment of the configuration for the action types listed above is shown in Listing 2. Let's look at the configuration of the action type
CreateCategory to understand this process. The action processor for this type returns the key "success" if a new category record is successfully created. The configuration maps the key "success" to the render type
AdminIndex. If the
CreateCategory action succeeds, the render type will be set to
AdminIndex, showing in the admin home page to the user. If there is a form validation error, the render type will be set to
CreateCategory. This will result in the category creation form displayed along with the form validation error message. If the user cancels the operation, s/he will be taken back to the admin home page, as specified by the mapping for the key
<action-types> <action-type name="CreateCategory" form="category" preCondition="admin" > <processor-result-mapping key="success" value="AdminIndex" message="faq.create_category_success_message" /> <processor-result-mapping key="cancel" value="AdminIndex" /> <processor-result-mapping key="validation-error" value="CreateCategory" /> </action-type> <action-type name="DeleteCategory" preCondition="admin" > <processor-result-mapping key="success" value="AdminIndex" /> <processor-result-mapping key="check-error" value="QuestionsExistError" /> </action-type> : : : </action-types>
Listing 2. Configuration of FAQ portlet action types
Setting Up Access Control
MVCPortlet allows portlet deployers to specify access control for each render or action type. The configuration file has a separate section that defines named access control conditions. The permissions section for the FAQ portlet is shown in Listing 3. The configuration defines one precondition that allows access only for the user admin. For the FAQ portlet, we want to allow anyone to view non-admin functionality; i.e., render types corresponding to the
VIEW mode. All other screens and actions should only be accessible to the user admin. This is achieved by specifying admin as the precondition name specified by the
preCondition attribute of applicable render and action types.
<pre-conditions> <pre-condition name="admin" class="com.nabhinc.rules.UserPrecondition"> <users>admin</users> </pre-condition> </pre-conditions>
Listing 3. Permissions configuration for the FAQ portlet
The MVCPortlet framework provides a number of built-in processors for performing database-related operations. In many cases, including the above FAQ portlet, you can develop fully functional portlets without writing any Java code. As a result, implementing the business logic of a portlet is reduced to simply configuring existing processors. Configuration and mapping of
RenderProcessors is very similar. A fragment of
RenderProcessorconfiguration for the FAQ portlet is shown in Listing 4. The fragment shows the
RenderProcessor for the render type
ViewIndex. The render processor name is
ViewIndex, which allows MVCPortlet to associate it with the render type with the same name. The configuration specifies the
SelectRecords render processor that retrieves a vector of database records and sets the attribute
mvcportlet.records to the record vector. The SQL for fetching the records is specified in
<sql>element specified in the processor configuration.
<render-processors> <render-processor name="ViewIndex" class="com.nabhinc.portlet.mvcportlet.renderprocessor.SelectRecords"> <sql>SELECT catid, cattitle, catrank FROM SB_FAQ_CATEGORIES ORDER BY catrank</sql> </render-processor> : : </render-processors>
Forms and Input Validation
The FAQ portlet requires user input when a category/question is being created or edited. Correspondingly, we have a form for accepting category information and a form for accepting question information. Listing 5 shows the forms configuration for the FAQ portlet. The configuration defines two forms:
question. A form is associated with an action type by specifying its name in the
form attribute of the
<action-type> element, as shown in
CreateCategory action type configuration shown in Listing 2.
<forms> <form name="category"> <field name="cat_id" /> <field name="cat_title" validators="required,maxNameLength" /> <field name="cat_rank" validators="int" default="0" /> </form> <form name="question"> <field name="cat_id" /> <field name="q_id" /> <field name="q_title" validators="required,maxNameLength" /> <field name="q_answer" validators="required,maxDescrLength" /> <field name="q_rank" validators="int" default="0" /> </form> </forms>
Listing 5. Forms configuration for the FAQ portlet
The form named
category defines three fields: category ID, category title, and category rank. Each field is optionally associated with one or more validators. For example, the field
cat_title references two validators named
maxNameLength, respectively. This is specified as a comma-delimited list in the
validators attribute of the
<field>element. The validators configuration section defines corresponding validators, as shown in Listing 6. Each validator has a name that must match the name specified in the form configuration. In addition, a validator can have additional attributes and sub-elements specific to that validator. For example, the
maxNameLength validator uses the validator class
com.nabhinc.portlet.mvcportlet.validator.MaxLengthValidatorwith a configuration attribute,
maxLength, that provides the maximum length for the form field value.
<validators> <validator name="required" class="com.nabhinc.portlet.mvcportlet.validator.RequiredValidator" /> <validator name="int" class="com.nabhinc.portlet.mvcportlet.validator.IntegerValidator" /> <validator name="maxNameLength" class="com.nabhinc.portlet.mvcportlet.validator.MaxLengthValidator" maxLength="100" /> <validator name="maxDescrLength" class="com.nabhinc.portlet.mvcportlet.validator.MaxLengthValidator" maxLength="4000" /> </validators>
Listing 6. Validators configuration for the FAQ portlet
Writing JSP Files
JSP files used to generate markup fragments can use all available aids, such as JSP tag libraries. The MVCPortlet framework provides a tag library and helper JSPs to facilitate this task. As an example, consider the JSP file shown in Listing 7 that displays the category creation form. The file first includes a helper JSP file, nav.jsp. This JSP file inserts various information messages set by
RenderProcessorobjects invoked during the current requests. It also inserts navigation links at the top of the page. The file also uses a number of JSP tags provided by the MVCPortlet framework, including:
<mp:form>: This tag results in an HTML form tag with the form action set to the action URL for the current portlet. In addition, if the attribute
validationMethodis specified, it generates client-side validation code for form fields.
<mp:formLabel>: This tag produces internationalized form field labels. If there was a validation error for the named form field, it also highlights those fields, depending on the portal theme styles.
<mp:input>: This tag generates a text input field and populates it based on the corresponding render parameter.
<%@include file="../include/nav.jsp" %> <% String catTitle = rReq.getParameter("cat_title"); if (catTitle == null) catTitle = ""; String catRank = rReq.getParameter("cat_rank"); if (catRank == null) catRank = "0"; PortletURL actionURL = rRes.createActionURL(); %> <table width="100%"> <mp:form name="category" validationMethod="validateCreateCategory"> <input type="hidden" name="request_type" value="CreateCategory" /> <tr> <td class="portlet-font"> <b><mp:formLabel key="faq.title_label" name="cat_title" /></b> </td> <td class="portlet-font"> <mp:input name="cat_title" /> </td> </tr> <tr> <td class="portlet-font"> <b><mp:formLabel key="faq.rank_label" name="cat_rank" /></b> </td> <td class="portlet-font"> <mp:input name="cat_rank" /> </td> </tr> <tr> <td class="portlet-font" align="right" colspan="2"> <br/> <mp:submit value="faq.create_category_button"/> <mp:cancel value="mvcportlet.cancel_button" /> </td> </mp:form> </table>
Listing 7. Example JSP
At this point we have completed all portlet development tasks and the FAQ portlet is ready for deployment. Actual deployment steps will, of course, depend on the chosen portal server. We tested the FAQ portlet in Stringbeans, an open source portal server. To deploy the FAQ portlet in this container, a portlet entry (shown in Listing 8) is copied to the portlet.xml file located in
WEB-INF directory of the portal web application. We then copied all JSP and configuration files to theportlets/faq directory under the web application root, and restarted the server. Finally, we added the portlet to an appropriate portal page to complete the portlet development and deployment process.
<portlet> <description>Displays Site FAQ.</description> <portlet-name>FAQ</portlet-name> <portlet-class> com.nabhinc.portlet.mvcportlet.core.ControllerPortlet </portlet-class> <init-param> <name>configFile</name> <value>/portlets/faq/portlet-config.xml</value> </init-param> <supports> <mime-type>text/html</mime-type> <portlet-mode>ADMIN</portlet-mode> </supports> <supports> <mime-type> application/vnd.wap.xhtml+xml </mime-type> </supports> <portlet-info> <title>Frequently Asked Questions</title> <keywords>Collaboration</keywords> </portlet-info> </portlet>
Listing 8. FAQ portlet configuration in portlet.xml
- JSR 168 specification home
- MVCPortlet project home page
- Stringbeans project home page
- Portlet community at java.net