Liferay portlet preferences using JSF

Goal

To manage the portlet preferences in Liferay when using JSF technology

Description

This recipe explains the basic steps to create a configurable portlet in Liferay with its own set of portlet preferences and how to create its own configuration page using Liferay faces portal bridge.

How to

First, we will define the configurable portlet in the portlet.xml (we will not show the necessary and specific configurations for this portlet in neither liferay-portlet.xml nor liferay-display.xml because none of those specifications are specific to this recipe’s goal):

<?xml version="1.0"?>

<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    version="2.0">
 <portlet>
   <portlet-name>MyPortlet</portlet-name>
   <display-name>My portlet</display-name>
   <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class>
   <init-param>
     <name>javax.portlet.faces.defaultViewId.view</name>
     <value>/views/myportlet/my_portlet.xhtml</value>
   </init-param>
   <init-param>
     <name>javax.portlet.faces.defaultViewId.edit</name>
     <value>/views/myportlet/my_portlet_configuration.xhtml</value>
   </init-param>
   <expiration-cache>0</expiration-cache>
   <supports>
     <mime-type>text/html</mime-type>
     <portlet-mode>view</portlet-mode>
     <portlet-mode>edit</portlet-mode>
   </supports>
   <resource-bundle>myportlet.Language</resource-bundle>
   <portlet-info>
     <title>My portlet</title>
     <short-title>My portlet</short-title>
     <keywords>My portlet</keywords>
   </portlet-info>
   <portlet-preferences>
     <preference>
       <name>booleanPreference</name>
       <value>false</value>
     </preference>
     <preference>
       <name>stringPreference</name>
       <value>String</value>
     </preference>
   </portlet-preferences>
   <security-role-ref>
     <role-name>site-owner</role-name>
   </security-role-ref>
 </portlet>

Next, we create a JSF backing bean to support our page edition (notice that we do not specify all our portlet preferences with the corresponding getters/setters methods. We only do that for the boolean property type and that is only to help the specification of the interface so that we may show a boolean input type):

package com.linkare.myportlet;

import java.util.Enumeration;
import java.util.Map;

import javax.el.ELResolver;
import javax.faces.FacesException;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.ActionResponse;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.ReadOnlyException;
import javax.portlet.WindowState;
import javax.portlet.faces.preference.Preference;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;

/**
 * 
 * @author Paulo Zenida - Linkare TI
 *
 */
@ManagedBean(name = "myConfigurationBean")
@RequestScoped
public class MyConfigurationBean implements Serializable {

  private static final long serialVersionUID = 4862752621992519880L;

  public static final String BOOLEAN_PREFERENCE = "booleanPreference";

  public static final String STRING_PREFERENCE = "stringPreference";

  private static final Log LOG = LogFactoryUtil.getLog(MyConfigurationBean.class);

  /**
   * Resets/restores the values in the portletPreferences.xhtml Facelet composition with portlet preference default values.
   */
  public void reset() {

    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = facesContext.getExternalContext();
    final PortletRequest portletRequest = (PortletRequest) externalContext.getRequest();
    final PortletPreferences portletPreferences = portletRequest.getPreferences();

    try {
      final Enumeration<String> preferenceNames = portletPreferences.getNames();

      while (preferenceNames.hasMoreElements()) {
        final String preferenceName = preferenceNames.nextElement();
        portletPreferences.reset(preferenceName);
      }

      portletPreferences.store();

      //Switch the portlet mode back to VIEW.
      final ActionResponse actionResponse = (ActionResponse) externalContext.getResponse();
      actionResponse.setPortletMode(PortletMode.VIEW);
      actionResponse.setWindowState(WindowState.NORMAL);

      addSuccessMessage(null, "Information", "Portlet preferences reset"));
    } catch (Exception e) {
      LOG.error(e);
      addErrorMessage(null, "Oops", "Portlet preferences not reset"));
    }
  }

  /**
   * Saves the values in the portletPreferences.xhtml Facelet composition as portlet preferences.
   */
  public void submit() {

    //The JSR 329 specification defines an EL variable named mutablePortletPreferencesValues that is being used in
    //the portletPreferences.xhtml Facelet composition. This object is of type Map<String, Preference> and is
    //designed to be a model managed-bean (in a sense) that contain preference values. However the only way to
    //access this from a Java class is to evaluate an EL expression (effectively self-injecting) the map into
    //this backing bean.
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = facesContext.getExternalContext();
    final String elExpression = "mutablePortletPreferencesValues";
    final ELResolver elResolver = facesContext.getApplication().getELResolver();
    @SuppressWarnings("unchecked")
    final Map<String, Preference> mutablePreferenceMap = (Map<String, Preference>) elResolver.getValue(facesContext.getELContext(), null, elExpression);

    //Get a list of portlet preference names.
    final PortletRequest portletRequest = (PortletRequest) externalContext.getRequest();
    final PortletPreferences portletPreferences = portletRequest.getPreferences();
    final Enumeration<String> preferenceNames = portletPreferences.getNames();

    try {
      //For each portlet preference name:
      while (preferenceNames.hasMoreElements()) {
        //Get the value specified by the user.
        final String preferenceName = preferenceNames.nextElement();
        final String preferenceValue = mutablePreferenceMap.get(preferenceName).getValue();

        //Prepare to save the value.
        if (!portletPreferences.isReadOnly(preferenceName)) {
            portletPreferences.setValue(preferenceName, preferenceValue);
        }
      }

      //Save the preference values.
      portletPreferences.store();

      //Switch the portlet mode back to VIEW.
      final ActionResponse actionResponse = (ActionResponse) externalContext.getResponse();
      actionResponse.setPortletMode(PortletMode.VIEW);
      actionResponse.setWindowState(WindowState.NORMAL);

      //Report a successful message back to the user as feedback.
      addSuccessMessage(null, "Information", "Portlet preferences changed"));
    } catch (Exception e) {
      LOG.error(e);
      addErrorMessage(null, "Oops", "Portlet preferences not changed");
    }
  }

  public boolean isBooleanPreference() {
    final Map<String, Preference> mutablePreferenceMap = getPrefMap();
    return Boolean.parseBoolean(mutablePreferenceMap.get(BOOLEAN_PREFERENCE).getValue());
  }

  public void setBooleanPreference(boolean booleanPreference) {
    try {
      final Map<String, Preference> mutablePreferenceMap = getPrefMap();
      final Preference isBooleanPreference = mutablePreferenceMap.get(BOOLEAN_PREFERENCE);
      isBooleanPreference.setValue(Boolean.toString(booleanPreference));
    } catch (ReadOnlyException e) {
      throw new FacesException(e);
    }
  }

  private Map<String, Preference> getPrefMap() {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final ELResolver elResolver = facesContext.getApplication().getELResolver();
    @SuppressWarnings("unchecked")
    final Map<String, Preference> mutablePreferenceMap = (Map<String, Preference>) elResolver.getValue(facesContext.getELContext(), null,
                                                       "mutablePortletPreferencesValues");
    return mutablePreferenceMap;
  }

  private static FacesMessage createFacesMessage(final Severity severity, final String summary, final String detail) {
    return new FacesMessage(severity, summary, detail);
  }

  private static void addSuccessMessage(final String clientId, final String summaryKey, final String detailKey) {
    FacesContext.getCurrentInstance().addMessage(clientId, createFacesMessage(FacesMessage.SEVERITY_INFO, summary, detail));
  }

  private static void addSuccessMessage(final String clientId, final String summaryKey, final String detailKey) {
    FacesContext.getCurrentInstance().addMessage(clientId, createFacesMessage(FacesMessage.SEVERITY_ERROR, summary, detail));
  }
}

And finally, we define the configuration page, as defined in the portlet.xml (/views/myportlet/my_portlet_configuration.xhtml). The template.xhtml is not shown because it is not relevant for the solution:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:c="http://java.sun.com/jsp/jstl/core"
    xmlns:p="http://primefaces.org/ui"
    template="/views/template/template.xhtml">

  <ui:define name="content">
  
    <h2>
      <h:panelGroup layout="block" styleClass="row-fluid">
        <i class="icon-wrench" />
        <h:outputText value="#{' '}" />
        <h:outputText value="My portlet preferences" />
      </h:panelGroup>
    </h2>

    <h:panelGroup layout="block" styleClass="row-fluid">
      <h:panelGroup layout="block" styleClass="span6">
        <p:outputLabel for="booleanPreference" value="Boolean preference" />
        <p:selectBooleanCheckbox id="booleanPreference" value="#{myConfigurationBean.booleanPreference}" />
      </h:panelGroup>
      <h:panelGroup layout="block" styleClass="span6">
        <p:outputLabel for="stringPreference" value="String preference" />
        <p:inputText id="stringPreference" value="#{mutablePortletPreferencesValues['stringPreference'].value}" />
      </h:panelGroup>
    </h:panelGroup>
  <ui:define>
</ui:composition>

Explanations

And it’s done. All important/useful informations are directly specified in the snippets shown above. Enjoy!

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s