Chapter 10. Personalizing portlets

This chapter covers

  • Personalizing portlet content and behavior
  • Defining portlet preferences
  • Validating portlet preferences
  • Using the portletPreferences implicit variable

How many times have you Googled something, been directed to a website that you hope will provide the information you’re looking for, and find a website that’s so cluttered with information that it’s useless and you close the browser out of frustration?

Often you’ll find information on websites that isn’t relevant or that doesn’t pique your interest. For instance, if you’re a Java developer interested only in Java books, you probably found yourself losing interest when the Book Portal in chapter 5 showed books from other categories, like .NET and software engineering. Or imagine looking at a website that provides stock quotes for all stocks, regardless of which stocks are in your portfolio. In this case, you’re usually only interested in the prices or news related to your stocks.

The extraneous information on a portal or website not only has the potential to adversely affect the user experience, but it may also drive customers away to websites that provide personalized content.

It isn’t necessary for a portlet to provide some level of personalization to its users. For instance, an announcement portlet in a B2E (business-to-employee) portal may not support any personalization options, because the business’s announcements can’t be personalized based on employee preference. Nevertheless, when you’re determining the requirements for a portlet, it’s important to focus on the personalization aspects, because incorporating them will have an impact on your portlet code. Implementing personalization features in web applications can be a huge pain. Fortunately, the inherent support for personalization in Java portlet technology makes it easier to develop portlets that enrich the user experience by providing relevant content and personalized behavior.

 

Note

Extra information is sometimes useful. Sometimes, when visiting a website, you’ll stumble upon some useful information that you weren’t looking for. The content of a website is generally designed around how best to serve its readers, based on usage patterns that have been observed over a period of time. For instance, when you look up a movie on IMDb (http://www.imdb.com/), it also displays recommended movies that you may be interested in.

 

In this chapter, we’ll look at personalization support in Java’s portlet technology and see how it can help you to quickly incorporate personalization features in your portlets. You’ll see how the Book Catalog portlet can be personalized to show books belonging to a particular category, like Java or .NET, and to prominently display books preferred by the users of the portlet, and so on.

We’ll begin this chapter with a quick introduction to personalization.

10.1. Introducing portlet personalization

Personalization of content plays an important role in enriching the user experience. It lets your application’s users specify the content that they’re interested in, and the application in turn honors each user’s preferences. Application personalization isn’t limited to personalizing content but can also apply to customizing the behavior of the application. For instance, the Gmail portlet in the iGoogle portal allows users to specify the number of emails they want to view in the portlet (personalization of content) and whether they want to open a selected email in the same or a new browser window (personalization of behavior), as shown in Figure 10.1.

Figure 10.1. The Gmail portlet provides personalization options for portlet content and behavior.

The Gmail portlet allows the user to select the number of messages that should be displayed when the portlet is in a normal window state. The portlet lets the user specify whether it should open email in the original Gmail web application and not within the portlet window; the portlet has limited features compared to the full Gmail web application.

The Java servlet technology isn’t designed to support the personalization of applications, so if a JSP- and servlet-based web application is required to support personalized content and behavior, it has to be taken care of in the design and implementation of the web application itself. For instance, a database designer will be responsible for creating a database structure for storing preferences; a developer will be responsible for writing code to save preferences in the database and read them again later.

If you’re using Java portlet technology, the portal server database provides the necessary structure to store user preferences for each portlet in the web portal. The portal server shields you from the database details of saving and retrieving the user preferences. The portal server reads the user preferences of the logged-in user from the database and makes them available to your portlets in the portlet request. As a developer, you’re only responsible for making use of the portlet API to retrieve user preferences and use them to personalize the content or behavior of the portlet.

 

Code Reference

The source code for the Book Catalog portlet discussed in this chapter is located in the ch10_BookCatalog folder. Before you build or deploy the portlet, make sure you change the value of the uploadFolder initialization parameter in the portlet deployment descriptor. Also set the liferay.portal.home property in the build.properties file to point to your Liferay Portal installation directory.

 

Now that you understand the basic concept of personalization, let’s dive deeper. First, let’s discuss the requirements of the Book Catalog portlet, which now include the personalization options. Once you understand the portlet’s personalization options, we’ll look at how they can be implemented in the portlet.

10.2. Personalization requirements for the Book Catalog portlet

The Book Catalog portlet’s content and behavior is personalized according to the preferences set by the user. Figure 10.2 shows the preferences that users can set for this portlet.

Figure 10.2. The Book Catalog portlet’s Preferences page shows the options that can be personalized by users, including the preferred category, case-sensitive or case-insensitive search, preferred book, and so on.

The user can specify the category of books (like, Java and .NET) to view on the Book Catalog portlet’s home page (see Figure 10.3). The user can select multiple categories from the list; if no categories are selected, all are shown.

Figure 10.3. The Book Catalog portlet’s home page shows personalized content to the user. Each book catalog entry shows the category to which the book belongs.

The user can specify whether the book search is case sensitive or case insensitive . In previous chapters, the Book Catalog portlet searched books in a case-sensitive manner. If the user doesn’t select the search type, it defaults to a case-sensitive search.

The user can specify the maximum number of books that should be displayed . The available options are 1000, 10, and 5. By default, a user will be shown all the books in the catalog.

The user can view a read-only option that says that the greeting message displayed on the home page of the Book Catalog has this format: “Hello! <username>”, where <username> is the name of the logged in user.

The user can enter the ISBN of the book that should be highlighted in the catalog display (see Figure 10.3). If there’s no book in the catalog corresponding to the ISBN entered here, an appropriate error message is displayed.

Clicking one of the Reset links resets the corresponding option to its default value. The Greetings Message personalization option doesn’t have a Reset link because it’s a read-only option.

The user can click Save to save the selected preferences.

Figure 10.3 shows the home page of the Book Catalog portlet, which displays the books in the catalog.

The book category information is displayed for each book in the catalog. If a user has chosen to view books belonging to certain categories on the preferences page, only books in those categories will be shown to the user. Because every book must be associated with a category, the book category information must be entered when adding a book to the catalog.

The book search will be either case sensitive or case insensitive depending upon the preference chosen by the user.

If the user has specified certain books as preferred books, these books will be highlighted in yellow in the catalog.

The greeting message format that was predefined for the Book Catalog portlet will be used to show the greeting message to the user.

 

Code Reference

The sample code for the Book Catalog portlet in this chapter is located in the ch10_BookCatalog folder of the book’s source code.

 

If your portlet needs to support personalization, you’ll typically follow these steps to implement personalization:

  • Show personalization options in EDIT mode
  • Save user preferences into a persistent store using the PortletPreferences object
  • If required, validate preferences set by the user before persisting them
  • Retrieve user preferences using the PortletPreferences object, and customize the content and behavior of the portlet based on the user preferences

In the following sections, we’ll look at each of these steps in detail.

10.3. Showing personalization options in EDIT mode

To show the personalization options in EDIT mode, you need to do the following:

  • Add support for EDIT portlet mode
  • Write a render method for EDIT portlet mode

Let’s look at how you can accomplish these tasks in the Book Catalog portlet.

10.3.1. Adding support for EDIT portlet mode

If you recall from chapter 2, the purpose of EDIT portlet mode is to show personalization options for the portlet. Figure 10.2 shows the personalization options applicable to the Book Catalog portlet; those options should be generated in EDIT portlet mode.

The first thing that you need to do is add support for EDIT portlet mode to the Book Catalog portlet. You can define support for EDIT mode in the portlet deployment descriptor:

<portlet>
  <portlet-name>bookCatalog</portlet-name>
   <portlet-class>
      chapter10.code.listing.base.BookCatalogPortlet
   </portlet-class>
   <supports>
     <mime-type>text/html</mime-type>
     <portlet-mode>view</portlet-mode>
     <portlet-mode>edit</portlet-mode>
   </supports>
</portlet>

The preceding portlet deployment descriptor defines support for EDIT portlet mode by using the <portlet-mode> subelement of the <supports> element. For more details on how to define support for portlet modes, refer to chapter 4.

 

Code Reference

Refer to the portlet.xml file in the ch10_BookCatalog folder to see how the <portlet-mode> element is used to support EDIT portlet mode in the Book Catalog portlet.

 

Now that the portlet supports EDIT portlet mode, let’s look at how you can show personalization options in EDIT mode.

10.3.2. Writing a render method for EDIT portlet mode

In chapter 2 (section 2.5.1), I mentioned that there’s a render method for every portlet mode supported by a portlet. To display the personalization options, you need to write a render method in your portlet class corresponding to EDIT mode.

The following code snippet shows the render method for EDIT mode:

@RenderMode(name = "edit")
public void showPrefs(RenderRequest request, RenderResponse response)
  throws IOException, PortletException {
    PortletRequestDispatcher dispatcher =
      getPortletContext().getRequestDispatcher(
        response.encodeURL("/WEB-INF/jsp/preferences.jsp")
         );
   dispatcher.include(request, response);
}

The showPrefs method in the preceding snippet is a render method in EDIT mode (specified by the @RenderMode annotation) that makes use of PortletRequestDispatcher to dispatch portlet requests to the preferences.jsp page. The preferences.jsp page shows all the personalization options applicable for the Book Catalog portlet, as shown in Figure 10.2. This means that generating content in EDIT (or HELP) portlet mode is no different than in VIEW mode; the purpose of the content varies but not how it’s generated.

 

Code Reference

Refer to the BookCatalogPortlet class’s showPrefs method in the ch10_BookCatalog folder to see how the @RenderMode annotation is used to mark a method as a render method for EDIT portlet mode.

 

Listing 10.1 shows the portion of the preferences.jsp page that shows the Preferred Category and Search Type options to the user.

 

Code Reference

Refer to the preferences.jsp page in the ch10_BookCatalog folder to see how other personalization options of the Book Catalog portlet are specified in the JSP page.

 

Listing 10.1. The preferences.jsp page

The JSP page shows the preferred book category options (Java, .NET, Mobile Technology, and Software Engineering) in a multiselect list . The book search options (case-sensitive or case-insensitive) are displayed in an HTML combo box .

 

Note

Some of the details have been removed from the preferences.jsp page in listing 10.1 in order to focus on how the preferences are displayed by a JSP page. Later in this chapter, you’ll see a more refined preferences.jsp page that shows how preference values that have been set by a user are displayed by the page.

 

The next step is to capture the portlet preferences specified by the user and save them in a persistent store.

10.4. Saving user preferences

The process of saving portlet preferences selected by the user can be divided into two steps:

1.  Retrieve portlet preferences selected by the user

2.  Save preferences in the persistent store using the PortletPreferences object

Let’s look at each of these steps in detail in the context of the Book Catalog portlet.

10.4.1. Retrieving user preferences from the portlet request

You can retrieve preferences selected by the user from the portlet request as request parameters. In the Book Catalog portlet, when the user clicks Save on the preferences page (see Figure 10.2), an action request is dispatched to the portlet. The selected preference category and search type values are available to the portlet class like any other HTML form fields.

The next listing shows how the BookCatalogPortlet class’s savePreferences action method retrieves the preferences selected by the user.

Listing 10.2. Retrieving user preferences from request in savePreferences method

The savePreferences method retrieves the preferred categories chosen by the user . The categories are represented as a multiselect list, so the ActionRequest’s getParameterValues method is used to retrieve all the selected categories. If you were to use the getParameterValue method instead of getParameterValues, it would return only the first value of the prefCategory request parameter.

 

Note

If a request parameter in your portlet is expected to contain multiple values, you should use the PortletRequest’s getParameterValues method to retrieve it in the portlet class. You can use the getParameterValue method for single-valued request parameters.

 

The savePreferences method retrieves the search type (case-sensitive or case-insensitive) chosen by the user . The search type is represented as an HTML combo box from which only one value can be selected, so the ActionRequest’s getParameterValue method is used to retrieve the selected search type.

 

Code Reference

Refer to the BookCatalogPortlet class’s savePreferences method in ch10_BookCatalog to see how other personalization options are retrieved from the request.

 

Now that you’ve retrieved the preferences selected by the user, it’s time to save the preferences in a persistent store.

10.4.2. Saving portlet preferences to the persistent store

Preferences set for a portlet need to live longer than the user’s session; they must be saved in a persistent store for future use. When the user comes back to your portal later, the portlets should show content based on the preferences that the user set earlier.

Does saving the preferences in a database or other persistent store mean that you have to create a database and table structure to store preferences? Does it mean that you have to write data access code to save and retrieve preferences from the database? If you’re using Java portlet technology, you don’t need to worry about databases, table structures, and data access layers. You only need to know the Portlet 2.0 API in order to save portlet preferences.

The following listing again shows the savePreferences action method of the Book Catalog portlet. It uses the Portlet 2.0 API to save preferences in a persistent store (such as a database, flat file, and so on).

Listing 10.3. Saving the user’s preferences in the savePreferences method

The savePreferences method uses the ActionRequest’s getPreferences method to obtain the PortletPreferences object. We’ll discuss this object later in the chapter, but for now you just need to know that it’s the object provided by the portlet container to help portlets write and read preferences to and from a persistent store.

The PortletPreferences object’s setValues method is used to set the values for the preferred book categories selected by the user.

You set the user’s search type preference in the PortletPreferences object using the setValue method. Note the use of the setValue method instead of setValues.

You call the PortletPreferences object’s store method to save the preferences that have been set so far into a persistent store.

 

Code Reference

Refer to the BookCatalogPortlet’s savePreferences method in the ch10_BookCatalog folder to see how all the preferences are set in the PortletPreferences object, and how they’re saved in the end.

 

Listing 10.3 shows how you’ll usually save preferences in the persistent store. There’s no need to write any data access code or create any table structures to persist user preferences. So where is the preference information getting saved? You saw in chapter 9 that portal servers are backed by a database (referred to as the portal database) that contains, among other things, information about the users of the portal server and the portlets deployed on the portal server. This database also contains table structures to store portlet preferences set by users. The PortletPreferences object, which is provided by the portal server to your portlets, knows how to save portlet preferences into the portal database or any other persistent store; it’s responsible for calling appropriate data access objects on the portal server to save the preferences.

Let’s look at the PortletPreferences object in detail to understand its purpose and methods.

10.5. Saving preferences with PortletPreferences

You saw in the previous section that the PortletPreferences object is used to save user preferences into a persistent store. The PortletPreferences object is obtained by the portlet request’s getPortletPreferences method, and it defines methods to retrieve, set, and store preferences.

In most situations, portlet developers treat the portal database as a black box, not worrying about how preferences are stored to or retrieved from it. The Portlet API shields portlet developers from details of the portal database by providing the PortletPreferences object, which is implemented by portal server vendors and encapsulates the logic for retrieving and storing preferences from and to the portal database.

In this section, we’ll focus on saving preferences to a persistent store using the PortletPreferences object. In section 10.7, we’ll discuss how you can obtain saved preferences from the PortletPreferences object.

Table 10.1 shows some of the important methods in the PortletPreferences interface and describes them briefly.

Table 10.1. PortletPreferences methods

Method

Description

String getValue(String key, String default) Returns the first String value associated with the preference identified by key. If the preference isn’t found, it returns default as the value of the preference.
String[] getValues(String key, String[] defaults) Returns the String values (as an array) associated with the preference identified by key. If the preference isn’t found, it returns defaults as the value of the preference.
void setValue(String key, String value) Sets the preference identified by key with a value identified by the value argument in the PortletPreferences object.
void setValues(String key, String[] values) Sets the preference identified by key with an array of values identified by the value argument in the PortletPreferences object.
void store() Saves preferences set in the PortletPreferences object to a persistent store.
void reset(String key) Resets the value of the preference identified by key to its default value.
boolean isReadOnly(String key) Checks whether the preference identified by key is read-only or modifiable.

You saw in the previous section how you can use the setValue, setValues, and store methods of the PortletPreferences object. Later in this chapter, we’ll discuss the use of the getValue and getValues methods to retrieve preferences to either personalize portlet content (and behavior) or to display the preferences already set by the user on a JSP page. For now, we’ll discuss the two most interesting methods of PortletPreferences: the store and reset methods.

The PortletPreferences object’s store method persists the preferences that you had previously set using the setValue and setValues methods. If you set preferences on the PortletPreferences object after calling the store method, those preferences won’t be persisted. To make this point clear, the next listing shows an example in which preferences are set after calling the store method.

Listing 10.4. Setting preferences after invoking the store method

In listing 10.4, the pref3 and pref4 preferences are set after the store method is invoked; the pref3 and pref4 preferences aren’t saved in the persistent store by the portal server.

 

Warning

If you don’t call the store method after setting the preferences, the preferences aren’t saved to the persistent store.

 

The store method has two key features: atomicity and consistency. Let’s look at atomicity.

10.5.1. Atomicity in saving preferences

The store method doesn’t return a success or failure flag to the calling portlet, but it’s guaranteed to be atomic; the store method either successfully saves all the preferences in the persistent store or none.

The store method call can fail because the persistent store is unavailable, or the values of the preferences don’t match the criteria set by the PreferencesValidator (discussed in section 10.6). The portal server or portlet container is responsible for ensuring that saving preferences with the store method is always an atomic operation.

10.5.2. Concurrency issues in saving preferences

If multiple threads attempt to concurrently write portlet preferences into the persistent store, this may result in data inconsistency. Concurrency issues are handled by the store method implementation provided by the portal server or portlet container.

 

Note

I mentioned in chapter 2 that a portlet’s render method (in any portlet mode) shouldn’t be used to change the portlet state. The PortletPreferences object’s store method follows this rule strictly, and if you invoke the store method in a portlet’s render method, in any portlet mode, it will result in an exception.

 

Let’s now look at the PortletPreferences interface’s reset method, which allows you to reset previously set preferences.

10.5.3. Resetting preferences

Figure 10.2 shows that the Book Catalog portlet includes Reset hyperlinks to allow users to reset the preferences to default values.

You can reset a preference by calling the PortletPreferences object’s reset method, as shown here:

prefs.reset("category");

Here, prefs is the PortletPreferences object, and category identifies the preference that needs to be reset.

What happens when you call the reset method? If the preference that you’re resetting has default values, the value of the preference is set to its default. If the preference doesn’t have default values, the preference is simply removed from the PortletPreferences object.

 

Note

The portlet specification doesn’t specify any mechanism to define default values for portlet preferences. What the default value for a preference is and where it needs to be retrieved from depends upon the portlet container implementation. If you define your portlet preferences with initial values in the portlet deployment descriptor (discussed later in this chapter), the reset method considers the values defined for the preference in the portlet deployment descriptor to be the default values.

 

The only way to remove a preference from the PortletPreferences object is to use the reset method (assuming that no default values exist for the preference). You might think that using the setValue or setValues methods could also remove a preference, but this isn’t the case; even the value null is considered a valid value for a preference.

If a preference is removed from the PortletPreferences object, the getValue and getValues methods will return the default value specified in these methods. If the preference was set to null using the setValue or setValues method, the getValue and getValues methods will return null as the return value.

Let’s look at the following code listing, which shows the effect of using the reset and setValue methods to remove a portlet preference.

Listing 10.5. Removing a preference using the reset and setValue methods

The value of the pref1 preference is set to null and pref2 is set to someValue using the setValue method . The value of the pref2 preference is reset using the reset method . The values of the pref1 and pref2 preferences are retrieved using the getValue method .

 

Code Reference

Refer to the BookCatalogPortlet’s resetPreference method in the ch10_BookCatalog portlet to see the PortletPreferences object’s reset method in use. The Reset links in the Book Catalog portlet send the name of the preference to be reset as a request parameter. The Book Catalog portlet’s resetPreference method retrieves the name of the preference from the portlet request and resets the preference.

 

In listing 10.5, the returned value of pref1 is null because null is a valid value for a preference. The returned value of pref2 is -1 because the reset method removed the pref2 preference from the PortletPreferences object (there were no default values found for pref2).

 

Tip

To remove a preference, don’t set its preference value to null by calling the setValue method. Instead, use the reset method to reset or remove the preference from the PortletPreferences object.

 

When you use the reset method, the change in the preference isn’t persisted automatically; you still need to call the PortletPreferences object’s store method to persist the changes.

Validating information entered by a user is an important aspect of any application design. Applications are designed on the assumption that the data available from the data store will be consistent and that the application will handle any known inconsistencies that may exist in the data. Because preferences are saved in the data store and are later used to customize portlet content or behavior, the preferences entered by the user may need to be validated before they’re saved in the persistent store.

Let’s look at how preferences entered by users can be validated.

10.6. Validating preferences

Earlier in this chapter, you saw how to retrieve preferences selected by the user from the request and set these preferences in the PortletPreferences object. The next logical step is to validate these preferences before saving them in the portal database (by calling the store method).

For instance, in the Book Catalog portlet, you need to confirm that the preferred ISBN entered by the user exists in the catalog before saving it. There are several benefits of validating the ISBN:

  • Users can’t enter just any number as a preferred ISBN, which guarantees that you won’t end up storing unnecessary ISBNs in the persistent store.
  • You don’t need to write code in the Book Catalog portlet to handle scenarios where the preferred ISBN isn’t found in the catalog.
  • If users enter a wrong ISBN by mistake, they’ll be notified before the preferences are saved. This will also avoid leaving the impression that the Book Catalog portlet has bugs.

In your portlet, you can validate the user preferences at two places during request processing:

  • Before setting the preferences in the PortletPreferences object. This is achieved by writing validation logic in the portlet class.
  • After setting the preferences in the PortletPreferences object and before invoking the store method. This is achieved by using the PreferencesValidator.

Let’s look at both of these approaches.

10.6.1. Validating preferences before setting them in PortletPreferences

This approach for validating preferences requires that you write validation logic in your portlet class. The following listing shows the Book Catalog portlet’s savePreferences method, which validates the user’s preferred ISBN before calling the PortletPreferences object’s store method.

Listing 10.6. Validating preferences before calling the store method

First, you create a java.util.Map to store the error keys and error messages for validation errors. Then you obtain a reference to the BookService that’s later used to verify whether the book corresponding to the user-entered ISBN exists in the catalog, and you also obtain the ISBN number from the action request.

You check to make sure that the ISBN number entered by the user isn’t blank and that it exists in the catalog . Then, if it exists in the catalog, you add the ISBN number to the PortletPreferences object with the prefBook key . If the entered ISBN number isn’t valid or if it doesn’t exist in the catalog, you add an error key and message to the errorMap.

If there are no validation errors, you call the PortletPreferences object’s store method to save the user preferences in a persistent store.

In listing 10.6, you can store errorMap as a request attribute and retrieve it in the render method of your portlet class (assuming that your portlet uses the javax.portlet.actionScopedRequestAttributes container-runtime option) to show the error messages on your JSP page. Alternatively, you can set a generic error message using the ActionResponse’s setRenderParameter method and use the render parameter value to show the error message on your JSP page.

This validation approach works well, but it clutters the code in your portlet class, which affects code readability. Most web frameworks provide abstract validation classes, or interfaces, that are implemented by the application code to keep validation logic outside of the request-processing logic. Similarly, the Portlet API provides the PreferencesValidator interface, which helps keep your preference-validation logic outside your portlet class.

Let’s look at how to validate preferences using the PreferencesValidator interface.

10.6.2. Validating preferences using PreferencesValidator

The PreferencesValidator interface of the Portlet API offers the benefit of keeping your preference-validation logic outside of your portlet class.

 

Note

Preference validation is different from business validation. Business validation is done in the portlet class or in a class that extends or implements the class or interface of the portlet framework that you’re using to create portlets. For instance, in chapter 8, the MyValidator class of the Book Catalog portlet implements Spring’s Validator interface to perform business validations.

 

This listing shows the BookCatalogPrefsValidator class, which implements the PreferencesValidator interface and provides the preference-validation logic.

Listing 10.7. BookCatalogPrefsValidator validation logic for preferences

The BookCatalogPrefsValidator implements the Portlet API PreferencesValidator interface. PreferencesValidator defines the validate method, which is implemented by the BookCatalogPrefsValidator class . The validate method retrieves the preferred book’s ISBN from the PortletPreferences object and uses BookService to check whether a book exists in the catalog with that ISBN. If no book matching the ISBN is found in the catalog, the class throws a ValidatorException .

 

Code Reference

See the BookCatalogPrefsValidator class in ch10_BookCatalog for the complete implementation of the preferences validator used by the Book Catalog portlet.

 

We’ll discuss how to configure BookCatalogPrefsValidator for the Book Catalog portlet later in this section. For now, you can assume that the validate method of BookCatalogPrefsValidator is called internally by the PortletPreferences object’s store method. In listing 10.7, the ValidatorException is a Portlet API exception class that must be thrown by the validate method of your validator class if validation fails. ValidatorException acts as an indicator to the store method that the validation of one or more preferences has failed for some reason, and that it must not save the preferences in the persistent store.

 

Note

BookCatalogPrefsValidator makes use of the BookService class to invoke the isUniqueISBN method. If you’re using the Spring Portlet MVC framework, a class like BookService is configured in the portlet application context XML file with a dependency on a data access object (DAO). In such cases, you can configure the preferences validator class in the portlet application context XML file (instead of portlet.xml), autowire dependencies, and programmatically validate preferences by invoking the validate method. Alternatively, you can use Spring’s @Configurable annotation along with load- or compile-time weaving to autowire dependencies into the preferences validator instance that is not created by Spring.

 

The ValidatorException class defines multiple constructors, and the one used in listing 10.7 accepts two parameters:

  • An exception message
  • A collection of preference keys that identify preferences that failed validation

The collection of preference keys can be used by the portlet class to display an appropriate error message. In listing 10.7, the exception message is “ISBN number does not exist in catalog,” and the collection of failed preferences keys contains a single entry: prefBookISBN.

 

Tip

It’s recommended that, if a preference fails validation, you keep the name of the failed preference key the same as the name of the preference key in the PortletPreferences object. For instance, in listing 10.7, the name of the preference key in the PortletPreferences object is prefBookISBN; if the validation fails for this preference, the same name is added to the collection of failed preferences keys that’s passed as an argument to the ValidatorException constructor.

 

In your validator class, you have the option to throw a ValidatorException as soon as validation fails for a preference, or you can keep adding keys of failed preferences to the collection and throw the ValidatorException at the end, as shown here:

List<String> failedKeys = new ArrayList<String>();
if(...) {
  failedKeys.add("pref1");
}
if(...) {
  failedKeys.add("pref2");
}
if(failedKeys.size() > 0) {
  throw new ValidatorException("Exception",failedKeys)
}

The preceding code continues adding keys of preferences that failed validation to the failedKeys list, and it throws a ValidatorException after all the preferences have been validated.

Later in this chapter, you’ll see how BookCatalogPortlet makes use of this collection of keys of preferences that failed validation in order to show appropriate validation error messages to the user.

10.6.3. Configuring PreferencesValidator in the portlet deployment descriptor

The BookCatalogPrefsValidator class that you created earlier must be configured in the portlet deployment descriptor to instruct the portlet container to use it to validate portlet preferences. The following listing shows how BookCatalogPrefsValidator is configured in the portlet deployment descriptor.

Listing 10.8. The BookCatalogPrefsValidator configuration

In this listing, the <preferencesvalidator> subelement of the <portletpreferences> element is used to define the custom PreferencesValidator class that will validate preferences for the bookCatalog portlet. It’s important to note that you must specify the fully qualified class name of your custom PreferencesValidator class.

 

Code Reference

Refer to the portlet.xml file in the ch10_BookCatalog folder to see the configuration of the preferences validator in the Book Catalog portlet.

 

Now that all the validation code is in BookCatalogPrefsValidator, the BookCatalogPortlet’s savePreferences method (shown earlier in listing 10.6) contains no preferences validation logic, as you can see here.

Listing 10.9. The BookCatalogPortlet's savePreferences method

First you check whether the user has entered the ISBN for a preferred book or not; if so, you set it in the PortletPreferences object . You surround the call to the store method with a try-catch block . If you’re using a custom PreferencesValidator to validate preferences, then it’s important to catch any ValidatorException in your portlet class to show appropriate validation error messages to the user. You add the collection of failed preference keys as a request attribute .

 

Warning

You must not call the PortletPreferences object’s store method from inside the validate method of the PreferencesValidator class because the portlet container constrains the validate method to perform validation only.

 

In listing 10.9, there’s a reason for checking the ISBN for a blank value before setting it in the PortletPreferences object. BookCatalogPrefsValidator is responsible for validating the preferences set in the PortletPreferences object, and should not be used to modify or change them in any way.

Let’s see what BookCatalogPrefsValidator would look like if you set the ISBN directly in the PortletPreferences object without doing a check for a blank entry. The modified BookCatalogPrefsValidator is shown next.

Listing 10.10. BookCatalogPrefsValidator checking if ISBN was entered

BookCatalogPrefsValidator checks whether the prefBookISBN preference is empty or null. The validator attempts to remove the prefBookISBN preference from the PortletPreferences object using the reset method, because saving blank or null as the value of the preferred book’s ISBN is invalid.

In listing 10.10, BookCatalogPrefsValidator isn’t only validating preferences; it’s also modifying the value of the prefBookISBN preference, which isn’t recommended. This is similar to using validators in the Spring or Struts frameworks; validators shouldn’t change the attributes of the object being validated.

 

Tip

If your portlet offers many personalization options, you may have multiple personalization pages, with each page showing a subset of options. In such cases, the validation code in PreferencesValidator can become cluttered. You can consider creating multiple PreferencesValidator classes and invoking them programmatically from your portlet class.

 

Figure 10.4 summarizes the role played by PreferencesValidator in portlet request processing.

Figure 10.4. The PreferencesValidator’s validate method is invoked when the store method of PortletPreferences is called by the portlet implementation class.

The action request for the BookCatalogPortlet is triggered when the user clicks Save (shown in Figure 10.2) to save the preferences.

The BookCatalogPortlet class’s savePreferences action method is invoked by the portlet container, which is responsible for saving preferences in the portal database. Then, the savePreferences method invokes the setValue or setValues method of PortletPreferences to set the preferences from the request parameters.

The savePreferences method calls the PortletPreferences object’s store method. The store method internally calls the BookCatalogPrefsValidator’s validate method, which in turn validates the preferences set in PortletPreferences. If the validate method doesn’t throw a ValidateException or any other exception, the store method saves the preferences in a persistent store . After this point, the normal portlet request-processing lifecycle continues.

Let’s now take a look at how you can show a customized exception message when a preference fails validation.

10.6.4. Handling ValidatorException

You may recall that the PreferencesValidator throws a ValidatorException (see listing 10.7) which is then caught by the portlet class (see listing 10.9) to display a generic exception message to the user. ValidatorException is limited to providing a collection of preference names indicating which ones failed validation, but that isn’t sufficient to show a user-friendly exception message in the portlet. In this section, we’ll discuss an approach that will allow you to show a user-friendly exception message in the Book Catalog portlet.

Figure 10.5 shows a custom exception message displayed by the Book Catalog portlet when the prefBookISBN preference fails validation: “Please enter a valid value for the ‘Preferred ISBN Number’ preference”.

Figure 10.5. The Book Catalog portlet shows a custom error message when the validation of one or more preferences fails.

A custom error message is shown to the user when the portlet fails to find a book with the ISBN entered by the user in the Preferred Book ISBN Number field. Let’s see how the Book Catalog portlet manages to show a custom error message when validation fails for a preference.

The next listing shows how the BookCatalogPortlet class makes use of the failed preferences names and keys, received with ValidatorException, to show a user-friendly message.

Listing 10.11. The BookCatalogPortlet class’s savePreferences method

First, you create an empty list in which you can store validation error messages. The store method of PortletPreferences is invoked . The preference name or key returned by ValidatorException is obtained using its getFailedKeys method.

The error message corresponding to a failed preference is obtained from the resource bundle. The name with which the error message is stored in the resource bundle follows this format:

javax.portlet.preference.<preference-name>.error

Here, <preference-name> is the name or key of the preference that failed validation. The error message in the resource bundle contains a placeholder for a user-friendly preference name or key, as shown here:

javax.portlet.preference.prefBookISBN.error =
   Please enter a valid value for the '{0}' preference

The placeholder for the preference name is identified by '{0}'.

Then you obtain the name of the preference from the resource bundle. The name of the preference follows this format:

javax.portlet.preference.<preference-name>.name

Here, <preference-name> is the name or key of the preference that failed validation. Then, with errorMessage.replace, you replace the preference name placeholder in the error message with its user-friendly name.

Finally, you add all the error messages to the request, which is then used by the JSP page to display the error messages.

Listing 10.11 shows one of the many possible ways you can display user-friendly validation error messages, and it’s sufficient in most portlet development scenarios.

Phew! We’ve made it through adding personalization support, validating preferences, and saving portlet preferences. We’ve covered a lot of ground, but it wasn’t too complicated. Stick with me for a few more pages and you’ll learn how to retrieve saved preferences and use them to customize the content and behavior of your portlets.

10.7. Retrieving portlet preferences and personalizing the portlet

Here’s the good news: you don’t need to do anything in your portlet implementation class to retrieve preferences from the persistent store. Really!

Let’s look at how you can access preferences in your portlet class and in the JSP pages included by the portlet. Later in this section, we’ll look at how portlet preferences can be used to personalize content and behavior.

10.7.1. Obtaining preferences in the portlet class and JSP pages

The Book Catalog portlet’s category preference attribute identifies the user’s preferred book category, like .NET, Java, or Software Engineering. (Listing 10.3 shows that the category preference is set in the BookCatalogPortlet class’s savePreferences method.) If the user had earlier saved .NET and Java as their preferred book categories, the user’s preference page (shown in Figure 10.2) should show the Java and .NET values selected in the list box. That means you need to retrieve the preferences from the persistent store in order to display them.

Like when saving preferences, you don’t need to know anything about the database or persistent store where saved preferences are kept. When your portlet’s request-processing methods, like render, serveResource, processAction, and processEvent, are invoked, the portlet container is responsible for reading the current values of the portlet preferences from the persistent store and making them available in the PortletPreferences object.

The following code snippet shows how you can obtain a PortletPreferences object in a portlet’s render method:

@RenderMode(name = "edit")
public void showPrefs(RenderRequest request, RenderResponse response)
  throws IOException, PortletException {
  PortletPreferences prefs = request.getPreferences();
  ...
}

In the preceding code snippet, the PortletRequest’s getPreferences method is used to obtain the PortletPreferences object. When you retrieve the PortletPreferences object with any of the request-processing methods, the portlet container guarantees it will provide saved preferences in the PortletPreferences object. If you had defined preferences with initial values in the portlet deployment descriptor, those preferences may also be available in the PortletPreferences object. Later in this chapter, we’ll discuss how to define preferences in the portlet deployment descriptor, but for now you can assume that if a preference is defined in the portlet deployment descriptor for which no values exist in the persistent store, the PortletPreferences object picks the preference and its values from the portlet deployment descriptor.

For instance, consider the following code snippet, which retrieves the pref1 preference:

@RenderMode(name = "edit")
public void showPrefs(RenderRequest request, RenderResponse response)
  throws IOException, PortletException {
  PortletPreferences prefs = request.getPreferences();
  String value = prefs.getValue("pref1", "-99");
  ...
}

In the preceding code, the value of the pref1 preference attribute depends upon whether the preference was earlier saved in the persistent store, whether it’s defined in the portlet deployment descriptor, and whether the persistent store is currently available. Let’s look at the pref1 preference attribute’s value in those different situations:

  • If the pref1 preference was saved in the persistent store, the value of the pref1 attribute in the preceding code is its current value in the persistent store.
  • If the pref1 preference was not saved in the persistent store and it was defined in the portlet deployment descriptor with an initial value, the value of the pref1 attribute is its initial value specified in the portlet deployment descriptor.
  • If the pref1 preference was saved in the persistent store and it’s defined in the portlet deployment descriptor with an initial value, but the persistent store isn’t accessible for some reason, the value of the pref1 attribute is its initial value specified in the portlet deployment descriptor.
  • If the pref1 preference was not saved in the persistent store, or if the persistent store isn’t accessible and pref1 wasn’t defined in the portlet deployment descriptor, the value of the pref1 attribute in the preceding code is -99.

Now that you know where the preference values come from, let’s get back to our initial goal of showing a user’s preferences on the preferences page. You can either retrieve preferences in the render method of EDIT mode, as described above, or directly in the JSP page by using the portletPreferences or portletPreferencesValues scripting variables introduced by the defineObjects tag of portlet tag library (portlet tag library tags were discussed in chapter 6). In most cases, directly retrieving portlet preferences in the JSP is sufficient for showing preferences.

The portletPreferences scripting variable represents a PortletPreferences object, and portletPreferencesValues represents a Map<String, String[]) object that contains a map of portlet preference names and values. You can use either of the two scripting variables in your JSP page to show preferences.

In listing 10.1, you saw how the preferences.jsp page of the Book Catalog portlet displays personalization options. Let’s now modify the preferences.jsp page to highlight the preferences that were already saved in the persistent store. The modified page is shown here.

Listing 10.12. The preferences.jsp page, modified to show saved preferences

This listing makes use of scripting variables to show current preference values for the Preferred Category and Greetings Message preferences that apply to the user. First, you use the defineObjects tag in the JSP page to introduce scripting variables, which makes it easy to develop included and forwarded JSP pages in portlets. You retrieve values for the category preference using the portletPreferences scripting variable ; then you show a book category as selected if it represents one of the values of the category preference. You show the value of the greetingMessage preference using the portletPreferencesValues scripting variable.

10.7.2. Personalizing content and behavior

So far in this chapter, you’ve learned about saving and retrieving preferences to and from the persistent store. The real value of the preferences shows up when you use them to customize the content and behavior of your portlet. In this section, we’ll look at how the Book Catalog portlet makes use of preferences to customize its behavior and content.

Table 10.2 revisits the preferences defined at the beginning of this chapter and describes their effect on the content and behavior of the Book Catalog portlet.

Table 10.2. Book Catalog preferences

Preference

Effect on content and behavior

Preferred Category Allows a user to personalize the Book Catalog portlet to show books from specified categories. For instance, if the user has chosen to view books in the Java and .NET categories, only books related to those categories are shown to the user when they visit the portal page.
Search Type Allows a user to choose between case-insensitive and case-sensitive searches for books.
Maximum Number of Books to Show Allows a user to specify the number of books that will be shown in the Book Catalog portlet. By default, the Book Catalog portlet shows up to 1000 books on the page.
Greetings Message Represents the greetings message that’s shown to the user on the portlet’s home page. It can’t be changed by the user and is shown as a read-only option.
Preferred Book ISBN Number If the user specifies one or more preferred books, the books are shown highlighted, on a yellow background, on the home page of the Book Catalog portlet.

In this section, we’ll look at how content is personalized based on the Preferred Category preference and how behavior is personalized based on the Search Type preference. The rest of the preferences in the Book Catalog portlet are implemented along similar lines.

 

Note

The personalization of content affects what information is displayed by the portlet. The personalization of behavior deals with how the portlet behaves in response to user actions.

 

Personalizing Content Based on Book Category Preference

The Book Catalog portlet either displays books from user-selected categories or it shows books from all categories. The next listing shows how the showBooks method of the BookCatalogPortlet class personalizes catalog content based on the book category.

Listing 10.13. The showBooks method personalizes content by category.

The showBooks method retrieves the PortletPreferences object from the portlet request. Then you obtain the values of the category preference as a String[], because there can be multiple values associated with this preference. You check whether the category preference contains any values. If it contains a single value of -99, that means that there are no values associated with the category preference.

All the books are obtained from the catalog if no valid value is found for the category preference. The BookService class’s getBooks method returns all books in the catalog, irrespective of the category to which they belong.

If valid category values exist , you obtain books belonging to those categories using the BookService’s getBooksByCategories method. You set the books that were obtained , either by the getBooks or getBooksByCategories method, as a request attribute.

The home JSP page of the Book Catalog portlet is then responsible for using the books stored in the request attribute to display the list of books in the catalog. The list may represent all the books in the catalog or the set of books that belong to the user’s preferred categories.

 

Code Reference

Refer to the BookCatalogPortlet class’s showBooks method in the ch10_BookCatalog folder to see how the Book Catalog portlet makes use of the category preference attribute to personalize content.

 

Listing 10.13 highlights some of the important decisions that you may have to make while writing code that uses preference information to customize portlet content or behavior. Here are two of these decisions:

  • If there’s a possibility that the preference may contain multiple values, you should use the PortletPreferences’s getValues method; otherwise use the getValue method.
  • If a preference can’t have a default value, the default value specified in the getValue or getValues method should be a value that the preference will never have. Suppose you specify .net as the default value in the getValues method that returns the user’s preferred book categories. If .net is also a valid value of the category preference, you won’t be able to figure out whether the user chose .net as their preference, or if it was returned by the getValues method because the category preference wasn’t found in the PortletPreferences object.

 

Code Reference

The source code in the ch10_BookCatalog folder shows how the Book Catalog portlet customizes content. The home.jsp page shows how content is personalized based on the Maximum Number of Books to Show and Greetings Message options. The <c:forEach> tag used in home.jsp puts a limit on the number of books to display by using its end attribute. It also uses the portletPreferencesValues scripting variable to read the value of the greetingMessage preference from the portlet.xml file to show the greeting message. Later in this chapter, we’ll discuss how you can define preferences in the portlet deployment descriptor.

 

Personalizing Behavior Based on the Search Type Preference

You can personalize the behavior of a portlet in the same way that you personalize portlet content. The Search Type preference of the Book Catalog portlet lets a user choose between case-sensitive and case-insensitive book catalog searches; instead of affecting the content displayed by the portlet, it affects the portlet’s behavior.

The following listing shows how the searchBooks method of the Book Catalog portlet personalizes search behavior based on the value of the Search Type preference.

Listing 10.14. The BookCatalogPortlet class’s searchBooks method

The implementation of the searchBooks method here is similar to the showBooks method in listing 10.13: both methods retrieve portlet preferences and personalize the content or behavior of the portlet. The BookCatalogPortlet class’s searchBooks action method obtains the PortletPreferences object from the request. You retrieve the value of the searchType preference , which identifies the search type: case-sensitive or case-insensitive, with the default being case-sensitive. The BookService class’s searchBooks method is invoked, and it’s passed the user’s search type preference. The searchBooks method makes use of the search type preference to perform a case-sensitive or case-insensitive search.

So far in this chapter, you’ve seen examples in which portlet preferences are defined in the portlet class. Let’s now look at how you can define preferences in the portlet deployment descriptor.

10.7.3. Defining portlet preferences in portlet.xml

Earlier in this chapter, you saw how you can define preferences on the fly, in the portlet code itself. Portlet preferences are similar to any other configuration option in the portlet.xml file—defining portlet preferences in portlet.xml informs the portlet deployer about the preferences supported by the portlet and their initial values.

You can define portlet preferences in the portlet deployment descriptor using the <preference> subelement of the <portletpreferences> element. The following listing shows how the Book Catalog portlet defines the maxNumOfBooks and greeting-Message preferences in the portlet.xml file.

Listing 10.15. Portlet preferences defined in portlet.xml

First you define a preference using the <preference> element . The <name> element is used to specify a name for the preference , and <value> elements are used to define initial values for the preference . The <read-only> element is used to mark a preference as read-only .

If you define portlet preferences in the portlet.xml file, not only does this increase the maintainability of portlet preferences, but it also provides the following additional features:

  • Defines initial values for preferences— The initial value of a preference is available to a portlet as long as the preference isn’t saved in the portlet container’s persistent store. Once preference values are saved, they’re available to the portlet, but the initial values are not. The initial values of a preference can be useful in showing default personalization options to the user.
  • Defines read-only preferences— A modifiable preference attribute can be modified by the processAction, processEvent, and serveResource methods. If you specify true as the value of a <read-only> element for a preference, you can’t modify the value the preference using the setValue, setValues, or reset methods. Any attempt to modify a read-only preference will result in a Read-OnlyException.

Defining preferences in the portlet deployment descriptor has the added advantage that it informs portlet deployers about various personalization options available for the portlet. It’s recommended that you define preferences in the portlet deployment descriptor, with initial values, to clearly indicate the preferences that are available for the portlet.

10.8. Summary

In this chapter, you saw just how easy it is to personalize portlets. You saw that the PortletPreferences object of Portlet 2.0 is at the heart of portlet personalization, because it lets you transparently save and retrieve user preferences.

If you liked the concept of preferences, you’re going to love inter-portlet communication, which is one of the most exciting features of portlet technology. That’s what the next chapter is about.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.47.169