Portlets

Content:

  1. Introduction
  2. Mechanics
  3. Examples
  4. JSF
  5. JSF portlets
  6. WSRP
  7. Final remarks

Introduction:

One of the more exotic features of Java EE web applications are portlets.

So what is a portlet?

A portlet is a self-contained web UI components that can be displayed in a web portal supporting the specific type of portlets.

Java portlets are standardized:

Version 1.0
JSR 168, October 2005
Version 2.0
JSR 286, June 2008
Version 3.0
JSR 362, April 2017

Some of the big well-known portals supporting Java portlets are:

It is worth noting that these portals are huge and come with a lot more functionality than just a portlet container.

More modest portals include Apache Pluto and Apache Jetspeed.

This portal and portlet technology was pretty hot around 2005-2010, but today such portal functionality is usually created using client side technology aka "HTML 5" solutions.

Mechanics:

The portal portlet model look like:

Portlet model

A portal consist og one or more pages.

A page consist of one or more portlets.

The actual layout is configurable and the default depends on the portal.

But one popular approach seems to be:

Portlets supports at least 3 modes:

View
where the main functionality reside whether readonly or readwrite of data
Help
with help information
Edit
modifying preferences (configuration)

Portlets can be in 3 states:

Minimized
just showing a menu bar for this portlet
Normal
showing multiple portlets in single page
Maximized
showing in full screen

Portlets get defined in WEB-INF/portlet.xml.

Examples:

Here are 3 examples illustrating some basic portlet functionality.

The examples are tested with Pluto and JetSpeed.

GuidePortlet:

Functionality:

GuidePortlet.java:

package demo.portlet;

import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class GuidePortlet extends GenericPortlet  {
    @Override
    public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspView")).include(request, response);
    }
    @Override
    public void doHelp(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspHelp")).include(request, response);
    }
}

The code simply include JSP page specified in portlet.xml.

guide_view.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<portlet:defineObjects/>
<h2>Portlets:</h2>
<dl>
<dt>Guide</dt>
<dd>Description of available portlets</dd>
<dt>Database</dt>
<dd>Browse database table</dd>
<dt>Links</dt>
<dd>Maintaining links collection</dd>
</dl>
<p>
Source: ${portletConfig.portletName}
</p>

guide_help.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<portlet:defineObjects/>
<h2>Help:</h2>
<p>
You can read about each portlet.
</p>
<p>
Source: ${portletConfig.portletName}
</p>

DatabasePortlet:

Functionality:

DatabasePortlet.java:

package demo.portlet;

import java.io.IOException;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import demo.logic.DatabaseManager;

public class DatabasePortlet extends GenericPortlet  {
    private static final String PREFS_DB_DRIVER = "driver";
    private static final String PREFS_DB_CONNECTION_URL = "connectionURL";
    private static final String PREFS_DB_USERNAME = "username";
    private static final String PREFS_DB_PASSWORD = "password";
    private static final String PREFS_PAGE_SIZE = "pagesize";
    private static final String DEFAULT_DB_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DEFAULT_DB_CONNECTION_URL = "jdbc:mysql://localhost/mysql";
    private static final String DEFAULT_DB_USERNAME = "root";
    private static final String DEFAULT_DB_PASSWORD = "";
    private static final String DEFAULT_PAGE_SIZE = "5";
    @Override
    public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        int start;
        if(request.getParameter("start") != null) {
            start = Integer.parseInt(request.getParameter("start"));
        } else {
            start = 0;
        }
        int page;
        if(request.getParameter("page") != null) {
            page = Integer.parseInt(request.getParameter("page"));
        } else {
            page = Integer.parseInt(request.getPreferences().getValue(PREFS_PAGE_SIZE, DEFAULT_PAGE_SIZE));
        }
        request.setAttribute("start",  start);
        request.setAttribute("page",  page);
        String driver = request.getPreferences().getValue(PREFS_DB_DRIVER, DEFAULT_DB_DRIVER);
        String conurl = request.getPreferences().getValue(PREFS_DB_CONNECTION_URL, DEFAULT_DB_CONNECTION_URL);
        String usr = request.getPreferences().getValue(PREFS_DB_USERNAME, DEFAULT_DB_USERNAME);
        String pwd = request.getPreferences().getValue(PREFS_DB_PASSWORD, DEFAULT_DB_PASSWORD);
        request.setAttribute("data",  DatabaseManager.getPage(driver, conurl, usr, pwd, start, page));
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspView")).include(request, response);
    }
    @Override
    public void doHelp(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspHelp")).include(request, response);
    }
}

The code:

DatabaseManager.java and T1.java are not important for this article:

package demo.logic;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class DatabaseManager {
    public static List<T1> getPage(String driver, String conurl, String usr, String pwd, int start, int page) {
        List<T1> res = new ArrayList<T1>();
        try {
            Class.forName(driver);
            try(Connection con = DriverManager.getConnection(conurl, usr, pwd)) {
                try(PreparedStatement pstmt = con.prepareStatement("SELECT f1,f2 FROM t1 LIMIT ?,?")) {
                    pstmt.setInt(1,  start);
                    pstmt.setInt(2,  page);
                    try(ResultSet rs = pstmt.executeQuery()) {
                        while(rs.next() ) {
                            res.add(new T1(rs.getInt(1), rs.getString(2)));
                        }
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return res;
    }
}

package demo.logic;

public class T1 {
    private int f1;
    private String f2;
    public T1(int f1, String f2) {
        this.f1 = f1;
        this.f2 = f2;
    }
    public int getF1() {
        return f1;
    }
    public void setF1(int f1) {
        this.f1 = f1;
    }
    public String getF2() {
        return f2;
    }
    public void setF2(String f2) {
        this.f2 = f2;
    }
}

database_view.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<portlet:defineObjects/>
<h2>Data:</h2>
<table border="1">
<tr>
<th>F1</th>
<th>F2</th>
</tr>
<c:forEach items="${data}" var="row">
<tr>
<td>${row.f1}</td>
<td>${row.f2}</td>
</c:forEach>
</table>
<c:if test="${start >= page}">
<portlet:renderURL var="prevurl">
<portlet:param name="start" value="${start - page}"/>
<portlet:param name="page" value="${page}"/>
</portlet:renderURL> 
<a href="${prevurl}">Previous</a>
</c:if>
<c:if test="${fn:length(data) == page}">
<portlet:renderURL var="nexturl">
<portlet:param name="start" value="${start + page}"/>
<portlet:param name="page" value="${page}"/>
</portlet:renderURL> 
<a href="${nexturl}">Next</a>
</c:if>
<p>
Source: ${portletConfig.portletName}
</p>

The JSP use JSTL for showing data and portlet tags to generate next and previous links.

database_help.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<portlet:defineObjects/>
<h2>Help:</h2>
<p>
Browse data. Use previous and next links to scroll.
</p>
<p>
Source: ${portletConfig.portletName}
</p>

LinksPortlet:

Functionality:

LinksPortlet.java:

package demo.portlet;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletPreferences;
import javax.portlet.ReadOnlyException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ValidatorException;

import demo.logic.Link;
import demo.logic.LinksManager;

public class LinksPortlet extends GenericPortlet  {
    static final String PREFS_DB_DRIVER = "driver";
    static final String PREFS_DB_CONNECTION_URL = "connectionURL";
    static final String PREFS_DB_USERNAME = "username";
    static final String PREFS_DB_PASSWORD = "password";
    static final String DEFAULT_DB_DRIVER = "com.mysql.jdbc.Driver";
    static final String DEFAULT_DB_CONNECTION_URL = "jdbc:mysql://localhost/mysql";
    static final String DEFAULT_DB_USERNAME = "root";
    static final String DEFAULT_DB_PASSWORD = "";
    @Override
    public void processAction(ActionRequest request, ActionResponse response) {
        String driver = request.getPreferences().getValue(PREFS_DB_DRIVER, DEFAULT_DB_DRIVER);
        String conurl = request.getPreferences().getValue(PREFS_DB_CONNECTION_URL, DEFAULT_DB_CONNECTION_URL);
        String usr = request.getPreferences().getValue(PREFS_DB_USERNAME, DEFAULT_DB_USERNAME);
        String pwd = request.getPreferences().getValue(PREFS_DB_PASSWORD, DEFAULT_DB_PASSWORD);
        String action = request.getParameter("action");
        switch(action) {
            case "add":
                Link link = new Link(request.getParameter("url"), request.getParameter("title"));
                LinksManager.add(driver, conurl, usr, pwd, link);
                break;
            case "delete":
                String linkurl = request.getParameter("url");
                LinksManager.delete(driver, conurl, usr, pwd, linkurl);
                break;
            case "save":
                PortletPreferences prefs = request.getPreferences();
                try {
                    prefs.setValue(PREFS_DB_DRIVER, request.getParameter("driver"));
                    prefs.setValue(PREFS_DB_CONNECTION_URL, request.getParameter("conurl"));
                    prefs.setValue(PREFS_DB_USERNAME, request.getParameter("usr"));
                    prefs.setValue(PREFS_DB_PASSWORD, request.getParameter("pwd"));
                    prefs.store();
                    response.setRenderParameter("error", "");
                } catch (ReadOnlyException e) {
                    e.printStackTrace();
                } catch (ValidatorException e) {
                    response.setRenderParameter("error", e.getMessage());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
    @Override
    public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        String driver = request.getPreferences().getValue(PREFS_DB_DRIVER, DEFAULT_DB_DRIVER);
        String conurl = request.getPreferences().getValue(PREFS_DB_CONNECTION_URL, DEFAULT_DB_CONNECTION_URL);
        String usr = request.getPreferences().getValue(PREFS_DB_USERNAME, DEFAULT_DB_USERNAME);
        String pwd = request.getPreferences().getValue(PREFS_DB_PASSWORD, DEFAULT_DB_PASSWORD);
        request.setAttribute("data",  LinksManager.getAll(driver, conurl, usr, pwd));
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspView")).include(request, response);
    }
    @Override
    public void doHelp(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspHelp")).include(request, response);
    }
    @Override
    public void doEdit(RenderRequest request, RenderResponse response) throws PortletException, IOException {
        response.setContentType("text/html");
        String driver = request.getPreferences().getValue(PREFS_DB_DRIVER, DEFAULT_DB_DRIVER);
        String conurl = request.getPreferences().getValue(PREFS_DB_CONNECTION_URL, DEFAULT_DB_CONNECTION_URL);
        String usr = request.getPreferences().getValue(PREFS_DB_USERNAME, DEFAULT_DB_USERNAME);
        String pwd = request.getPreferences().getValue(PREFS_DB_PASSWORD, DEFAULT_DB_PASSWORD);
        request.setAttribute("driver", driver);
        request.setAttribute("conurl", conurl);
        request.setAttribute("usr", usr);
        request.setAttribute("pwd", pwd);
        request.setAttribute("error", request.getParameter("error"));
        getPortletContext().getRequestDispatcher(getPortletConfig().getInitParameter("jspEdit")).include(request, response);
    }
}

The processAction method distinguishes between:

LinksPreferencesValidator.java:

package demo.portlet;

import java.util.regex.Pattern;

import javax.portlet.PortletPreferences;
import javax.portlet.PreferencesValidator;
import javax.portlet.ValidatorException;

import static demo.portlet.LinksPortlet.*;

public class LinksPreferencesValidator implements PreferencesValidator {
    private static final String NAME = "[A-Za-z][A-Za-z0-9]*";
    private static final String NAMES_PERIOD = NAME + "(\\." + NAME + ")*";
    private static final String NAMES_COLON= NAME + "(:" + NAME + ")*";
    private static final String NUMBER = "[0-9]+";
    private static final Pattern urlpat = Pattern.compile("^" + NAMES_COLON + "://" + NAMES_PERIOD + "(:" + NUMBER + ")?/" + NAME + "$");
    private static final Pattern clzpat = Pattern.compile("^" + NAMES_PERIOD + "$");
    private static final Pattern usrpat = Pattern.compile("^" + NAME + "$");
    private static final Pattern pwdpat = Pattern.compile("^(" + NAME + "|)$");
    @Override
    public void validate(PortletPreferences prefs) throws ValidatorException {
        String driver = prefs.getValue(PREFS_DB_DRIVER, DEFAULT_DB_DRIVER);
        String conurl = prefs.getValue(PREFS_DB_CONNECTION_URL, DEFAULT_DB_CONNECTION_URL);
        String usr = prefs.getValue(PREFS_DB_USERNAME, DEFAULT_DB_USERNAME);
        String pwd = prefs.getValue(PREFS_DB_PASSWORD, DEFAULT_DB_PASSWORD);
        if(!urlpat.matcher(conurl).find()) {
            throw new ValidatorException("Invalid URL: " + conurl, null);
        }
        if(!clzpat.matcher(driver).find()) {
            throw new ValidatorException("Invalid driver name: " + driver, null);
        }
        if(!usrpat.matcher(usr).find()) {
            throw new ValidatorException("Invalid username: " + usr, null);
        }
        if(!pwdpat.matcher(pwd).find()) {
            throw new ValidatorException("Invalid password", null);
        }
    }
}

The validate method use simple regex to validate preferences values.

LinksManager.java and Link.java are not important for this article:

package demo.logic;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class LinksManager {
    public static List<Link> getAll(String driver, String conurl, String usr, String pwd) {
        List<Link> res = new ArrayList<Link>();
        try {
            Class.forName(driver);
            try(Connection con = DriverManager.getConnection(conurl, usr, pwd)) {
                try(PreparedStatement pstmt = con.prepareStatement("SELECT url,title FROM links")) {
                    try(ResultSet rs = pstmt.executeQuery()) {
                        while(rs.next() ) {
                            res.add(new Link(rs.getString(1), rs.getString(2)));
                        }
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return res;
    }
    public static void add(String driver, String conurl, String usr, String pwd, Link link) {
        try {
            Class.forName(driver);
            try(Connection con = DriverManager.getConnection(conurl, usr, pwd)) {
                try(PreparedStatement pstmt = con.prepareStatement("INSERT INTO links VALUES(?, ?)")) {
                    pstmt.setString(1, link.getUrl());
                    pstmt.setString(2, link.getTitle());
                    pstmt.executeUpdate();
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void delete(String driver, String conurl, String usr, String pwd, String linkurl) {
        try {
            Class.forName(driver);
            try(Connection con = DriverManager.getConnection(conurl, usr, pwd)) {
                try(PreparedStatement pstmt = con.prepareStatement("DELETE FROM Links WHERE url = ?")) {
                    pstmt.setString(1, linkurl);
                    pstmt.executeUpdate();
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
package demo.logic;

public class Link {
    private String url;
    private String title;
    public Link(String url, String title) {
        this.url = url;
        this.title = title;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

links_view.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<portlet:defineObjects/>
<h2>Links collection:</h2>
<table>
<c:forEach items="${data}" var="row">
<tr>
<td style="vertical-align:top"><a href="${row.url}">${row.title}</a></td>
<td>
<portlet:actionURL var="delete" portletMode="view">
<portlet:param name="action" value="delete"/>
<portlet:param name="url" value="${row.url}"/>
</portlet:actionURL>
<form method="post" action="${delete}"><input type="submit" value="Delete"></form>
</td>
</tr>
</c:forEach>
</table>
<h2>Add link:</h2>
<portlet:actionURL var="add" portletMode="view">
<portlet:param name="action" value="add"/>
</portlet:actionURL>
<form method="post" action="${add}"">
URL: <input type="text" name="url">
<br>
Title: <input type="text" name="title">
<br>
<input type="submit" value="Add">
</form>
<p>
Source: ${portletConfig.portletName}
</p>

The JSP use a mix of JSTL and portlet tags.

links_help.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<portlet:defineObjects/>
<h2>Help:</h2>
<p>
Maintain links collection.
</p>
<p>
Source: ${portletConfig.portletName}
</p>

links_edit.jsp:

<%@ page language="java" %>
<%@ page session="false" %>
<%@ page import="javax.portlet.*" %>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<portlet:defineObjects/>
<h2>Links preferences:</h2>
<portlet:actionURL var="save" portletMode="edit">
<portlet:param name="action" value="save"/>
</portlet:actionURL>
<form method="post" action="${save}">
JDBC driver: <input type="text" size="64" name="driver" value="${driver}">
<br>
JDBC connection URL: <input type="text" size="64" name="conurl" value="${conurl}">
<br>
JDBC username: <input type="text" size="32" name="usr" value="${usr}">
<br>
JDBC password: <input type="password" size="32" name="pwd" value="${pwd}">
<br>
<input type="submit" value="Save">
</form>
<c:if test="${fn:length(error) > 0}">
Error: ${error}
</c:if>
<p>
Source: ${portletConfig.portletName}
</p>

The JSP is really just a simple form.

Packaging:

Everything just get packaged in a normla war file with a WEB-INF/portlet.xml that defined the portlets.

portlet.xml:

<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>GuidePortlet</portlet-name>
        <display-name>Guide to portal</display-name>
        <portlet-class>demo.portlet.GuidePortlet</portlet-class>
        <init-param>
            <name>jspView</name>
            <value>/guide_view.jsp</value>
        </init-param>
        <init-param>
            <name>jspHelp</name>
            <value>/guide_help.jsp</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
            <portlet-mode>HELP</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>  
        <portlet-info>
            <title>Guide to portal.</title>
            <short-title>Guide.</short-title>
            <keywords>guide help portal</keywords>
        </portlet-info>
    </portlet>
    <portlet>
        <portlet-name>DatabasePortlet</portlet-name>
        <display-name>Database browse</display-name>
        <portlet-class>demo.portlet.DatabasePortlet</portlet-class>
        <init-param>
            <name>jspView</name>
            <value>/database_view.jsp</value>
        </init-param>
        <init-param>
            <name>jspHelp</name>
            <value>/database_help.jsp</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
            <portlet-mode>HELP</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>        
        <portlet-info>
            <title>Database browse.</title>
            <short-title>Database.</short-title>
            <keywords>database browse</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>driver</name>
                <value>com.mysql.jdbc.Driver</value>
            </preference>
            <preference>
                <name>connectionURL</name>
                <value>jdbc:mysql://localhost/Test</value>
            </preference>
            <preference>
                <name>username</name>
                <value>root</value>
            </preference>
            <preference>
                <name>password</name>
                <value></value>
            </preference>
            <preference>
                <name>pagesize</name>
                <value>3</value>
            </preference>
        </portlet-preferences>      
    </portlet>
    <portlet>
        <portlet-name>LinksPortlet</portlet-name>
        <display-name>Links collection</display-name>
        <portlet-class>demo.portlet.LinksPortlet</portlet-class>
        <init-param>
            <name>jspView</name>
            <value>/links_view.jsp</value>
        </init-param>
        <init-param>
            <name>jspHelp</name>
            <value>/links_help.jsp</value>
        </init-param>
        <init-param>
            <name>jspEdit</name>
            <value>/links_edit.jsp</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
            <portlet-mode>HELP</portlet-mode>
            <portlet-mode>EDIT</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>        
        <portlet-info>
            <title>Links collection.</title>
            <short-title>Links.</short-title>
            <keywords>links urls collection maintaining</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>driver</name>
                <value>com.mysql.jdbc.Driver</value>
            </preference>
            <preference>
                <name>connectionURL</name>
                <value>jdbc:mysql://localhost/Test</value>
            </preference>
            <preference>
                <name>username</name>
                <value>root</value>
            </preference>
            <preference>
                <name>password</name>
                <value></value>
            </preference>
            <preferences-validator>demo.portlet.LinksPreferencesValidator</preferences-validator>
        </portlet-preferences>      
    </portlet>
</portlet-app>

Empty web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
</web-app>

The "English verbiage" is useful when managing portlets through an admin GUI.

Result:

Screenshot

JSF:

Obviously the above presentation technology (JSP and JSTL) seems a bit primitive.

As described here then Java EE got a new presentation technology: JSF. And JSF with managed beans simplifies forms and form processing a lot compared to plain JSTL tags and Java code.

Let us start be showing a couple of simple JSF examples.

Implementing same functionality as LinksPortlet in JSF using different view technology:

Alle of them use this LinksBean.java:

package demo.jsf;

import java.util.List;

// For JSF 2.3 use:
//
//import javax.inject.Named;
// import javax.enterprise.context.RequestScoped;
//
// and:
//
// @Named
// @RequestScoped

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import demo.logic.Link;
import demo.logic.LinksManager;

@ManagedBean
@RequestScoped
public class LinksBean {
    public List<Link> getData() {
        return LinksManager.getAll("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/Test", "root", "");
    }
    public void delete(String url) {
        LinksManager.delete("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/Test", "root", "", url);
    }
    private String url;
    private String title;
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public void add() {
        LinksManager.add("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/Test", "root", "", new Link(url, title));
    }
}

Very simple code.

The database connection information should of course be configurable, but since it is not portlet prefrences then it is out of scope from this article.

LinksManager.java and Link.java are the same as above.

JSF1.jsp for JSP view:

<%@ page language="java" %>
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<f:view>
<h2>Links collection:</h2>
<table>
<c:forEach items="#{linksBean.data}" var="row">
<tr>
<td style="vertical-align:top"><a href='<h:outputText value="#{row.url}"/>'"><h:outputText value="#{row.title}"/></a></td>
<td><h:form><h:commandButton action="#{linksBean.delete(row.url)}" value="Delete"/></h:form></td>
</tr>
</c:forEach>
</table>
<h2>Add link:</h2>
<h:form>
URL: <h:inputText id="url" value="#{linksBean.url}"/>
<br>
Title: <h:inputText id="title" value="#{linksBean.title}"/>
<br>
<h:commandButton action="#{linksBean.add}" value="Add"/>
</h:form>
</f:view>

JSF2.xhtml for facelet view:

<html 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">
<h:body>
<h2>Links collection:</h2>
<table>
<ui:repeat value="#{linksBean.data}" var="row">
<tr>
<td style="vertical-align:top"><h:outputLink value="#{row.url}"><h:outputText value="#{row.title}"/></h:outputLink></td>
<td><h:form><h:commandButton action="#{linksBean.delete(row.url)}" value="Delete"/></h:form></td>
</tr>
</ui:repeat>
</table>
<h2>Add link:</h2>
<h:form>
URL: <h:inputText id="url" value="#{linksBean.url}"/>
<br/>
Title: <h:inputText id="title" value="#{linksBean.title}"/>
<br/>
<h:commandButton action="#{linksBean.add}" value="Add"/>
</h:form>
</h:body>
</html>

JSF3.xhtml for inverted facelet view:

<html 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">
<body>
<h2>Links collection:</h2>
<table>
<tr jsfc="ui:repeat" value="#{linksBean.data}" var="row">
<td style="vertical-align:top"><span jsfc="h:outputLink" value="#{row.url}"><span jsfc="h:outputText" value="#{row.title}"/></span></td>
<td><form jsfc="h:form"><input type="submit" jsfc="h:commandButton" action="#{linksBean.delete(row.url)}" value="Delete"/></form></td>
</tr>
</table>
<h2>Add link:</h2>
<form jsfc="h:form">
URL: <input type="text" jsfc="h:inputText" id="url" value="#{linksBean.url}"/>
<br/>
Title: <input type="text" jsfc="h:inputText" id="title" value="#{linksBean.title}"/>
<br/>
<input type="submit" jsfc="h:commandButton" action="#{linksBean.add}" value="Add"/>
</form>
</body>
</html>

web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
</web-app>

For more info about JSF see this article.

JSF portlets:

It could be cool to be able to be able to use JSF technology for portlets.

The good news is that in theory it is very easy.

It works like:

Portlet and JSF

And all that is needed to make this work is a portlet.xml to define the portlets and a working portlet JSF bridge.

portlet.xml:

<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>LinksPortletJSF1</portlet-name>
        <display-name>Links collection JSF 1</display-name>
        <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.view</name>
            <value>/JSF1.jsp</value>
        </init-param>
        <init-param>
            <name>javax.portlet.faces.preserveActionParams</name>
            <value>true</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>        
        <portlet-info>
            <title>Links collection (JSF 1).</title>
            <short-title>Links (JSF 1).</short-title>
            <keywords>links urls collection maintaining</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>driver</name>
                <value>com.mysql.jdbc.Driver</value>
            </preference>
            <preference>
                <name>connectionURL</name>
                <value>jdbc:mysql://localhost/Test</value>
            </preference>
            <preference>
                <name>username</name>
                <value>root</value>
            </preference>
            <preference>
                <name>password</name>
                <value></value>
            </preference>
        </portlet-preferences>      
    </portlet>
        <portlet>
        <portlet-name>LinksPortletJSF2</portlet-name>
        <display-name>Links collection JSF 2</display-name>
        <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.view</name>
            <value>/JSF2.xhtml</value>
        </init-param>
        <init-param>
            <name>javax.portlet.faces.preserveActionParams</name>
            <value>true</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>        
        <portlet-info>
            <title>Links collection (JSF 2).</title>
            <short-title>Links (JSF 2).</short-title>
            <keywords>links urls collection maintaining</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>driver</name>
                <value>com.mysql.jdbc.Driver</value>
            </preference>
            <preference>
                <name>connectionURL</name>
                <value>jdbc:mysql://localhost/Test</value>
            </preference>
            <preference>
                <name>username</name>
                <value>root</value>
            </preference>
            <preference>
                <name>password</name>
                <value></value>
            </preference>
        </portlet-preferences>      
    </portlet>
        <portlet>
        <portlet-name>LinksPortletJSF3</portlet-name>
        <display-name>Links collection JSF 3</display-name>
        <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.view</name>
            <value>/JSF3.xhtml</value>
        </init-param>
        <init-param>
            <name>javax.portlet.faces.preserveActionParams</name>
            <value>true</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>        
        <portlet-info>
            <title>Links collection (JSF 3).</title>
            <short-title>Links (JSF 3).</short-title>
            <keywords>links urls collection maintaining</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>driver</name>
                <value>com.mysql.jdbc.Driver</value>
            </preference>
            <preference>
                <name>connectionURL</name>
                <value>jdbc:mysql://localhost/Test</value>
            </preference>
            <preference>
                <name>username</name>
                <value>root</value>
            </preference>
            <preference>
                <name>password</name>
                <value></value>
            </preference>
        </portlet-preferences>      
    </portlet>
</portlet-app>

The bad news is that this requires a lot of stuff (portlet container, bridge and JSF) to work together and it can be tricky to get it working.

There are standards, but those standards are very specific and behind current versions:

JSR 301
defines Portlet 1.0 JSF 1.2 integration, July 2010
JSR 329
defines Portlet 2.0 JSF 1.2 integration, August 2015
JSR 378
defines Portlet 3.0 JSF 2.2 integration, still in progress

I found it very difficult to get it working.

I assume those extremely expensive enterprise portal products comes with all this pre-configured.

WSRP:

WSRP (Web Services for Remote Portlets) is an OASIS standard for using remote portlets.

Version 1.0 was released in August 2003 and version 2.0 was released in April 2008.

It is a web service standard on top of SOAP.

It works like:

Portlet and WSRP

It is supported by the big players:

I don't think there is any interest in WSRP today. Portlets has become a de facto Java technology with limited support by Microsoft and PHP. And besides SOAP are not in fashion for web sites today.

Final remarks:

Article history:

Version Date Description
1.0 July 10th Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj