Portlets are a common technology to create plug&play components for modern web applications and are specified by the Java Community Process in several specification requests.

portlet logo

In the following tutorial we’re going to learn how to create custom portlets and how to deploy and embed them in Liferay, the popular open-source enterprise portal.

In addition we’re taking a look at inter-portlet-communication and how to create portlets using annotations.

Finally we’re building a portlet-state-aware Java-Server-Faces portlet using the jsf-portlet-bridge mechanism.

Prerequisites

A few things are needed before we can start to write our first portlets so please be sure to have the following applications installed:

I have used Liferay 6.0 GA4 Community Edition for this tutorial, bundled with GlassFish as application server because I like GlassFish – but Liferay is shipped with a rich variety of bundled execution environments so you may use GlassFish with a Tomcat, JBoss or Resin server if you wish to.

Creating a project with Maven

First we need a new Maven project – fortunately there is a Maven archetype for Liferay portlets .. it creates a standard wep application directory structure, dependencies needed, the portlet descriptor and some additional xml descriptor files for Liferay. If you’re not going to use Liferay you might want to delete the additional xml descriptors (or simple use the archetype org.apache.maven.archetypes:maven-archetype-portlet)

  • We’re creating a new Maven project using the archetype com.liferay.maven.archetypes:liferay-portlet-archetype and your Maven-enabled IDE our via console

    mvn archetype:generate
  • The generated pom.xml looks like this

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hascode.tutorial</groupId>
    <artifactId>liferay-portlet-tutorial</artifactId>
    <packaging>war</packaging>
    <name>liferay-portlet-tutorial Portlet</name>
    <version>0.0.1-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>com.liferay.maven.plugins</groupId>
                <artifactId>liferay-maven-plugin</artifactId>
                <version>${liferay.version}</version>
                <configuration>
                    <autoDeployDir>${liferay.auto.deploy.dir}</autoDeployDir>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>com.liferay.portal</groupId>
            <artifactId>portal-service</artifactId>
            <version>${liferay.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.liferay.portal</groupId>
            <artifactId>util-bridges</artifactId>
            <version>${liferay.version}</version>
        </dependency>
        <dependency>
            <groupId>com.liferay.portal</groupId>
            <artifactId>util-taglib</artifactId>
            <version>${liferay.version}</version>
        </dependency>
        <dependency>
            <groupId>com.liferay.portal</groupId>
            <artifactId>util-java</artifactId>
            <version>${liferay.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.portlet</groupId>
            <artifactId>portlet-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <properties>
        <liferay.auto.deploy.dir>../bundles/liferay-portal-6.0/deploy</liferay.auto.deploy.dir>
        <liferay.version>6.0.6</liferay.version>
    </properties>
    </project>
  • If you need more detailed information on the archetype, take a look at Milen Dyankovs blog article “http://milen.commsen.com/2009/10/creating-liferay-portlet-with-liferay-maven-sdk.html[Creating Liferay portlet with liferay-maven-sdk]“

  • Now we’re ready to build our first sample portlet .. everything we need for now is included as a dependency in the pom.xml .. most important for us: portlet-api, servlet api and jsp …

    $ mvn dependency:tree
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'dependency'.
    [INFO] ------------------------------------------------------------------------
    [INFO] Building liferay-portlet-tutorial Portlet
    [INFO]    task-segment: [dependency:tree]
    [INFO] ------------------------------------------------------------------------
    [INFO] [dependency:tree {execution: default-cli}]
    [INFO] com.hascode.tutorial:liferay-portlet-tutorial:war:0.0.1-SNAPSHOT
    [INFO] +- com.liferay.portal:portal-service:jar:6.0.6:provided
    [INFO] +- com.liferay.portal:util-bridges:jar:6.0.6:compile
    [INFO] +- com.liferay.portal:util-taglib:jar:6.0.6:compile
    [INFO] +- com.liferay.portal:util-java:jar:6.0.6:compile
    [INFO] +- javax.portlet:portlet-api:jar:2.0:provided
    [INFO] +- javax.servlet:servlet-api:jar:2.4:provided
    [INFO] \- javax.servlet.jsp:jsp-api:jar:2.0:provided
    [INFO] ------------------------------------------------------------------------

Building Portlets

Now that we got a Maven project set up we’re going to create our first portlet …

Obligatory Hello-World-Portlet

Our first portlet is the obligatory Hello-World application ..

  • First our simple portlet class that renders some text: com.hascode.tutorial.portlet.HelloWorldPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    
    public class HelloWorldPortlet extends GenericPortlet {
    	@Override
    	protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
    		response.setContentType("text/html");
    		PrintWriter writer = response.getWriter();
    		writer.println("<h1>Hello world</h1><br/><br/><div>This is a sample portlet from hasCode.com</div>");
    	}
    }
  • In addition we’re going to declare the portlet in the portlet.xml in src/main/webapp/WEB-INF

    <?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" version="2.0" 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">
    <portlet>
        <portlet-name>hascode-liferay-portlet</portlet-name>
        <display-name>hascode-liferay-portlet</display-name>
        <portlet-class>com.hascode.tutorial.portlet.HelloWorldPortlet</portlet-class>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <portlet-info>
            <title>hascode-liferay-portlet</title>
            <keywords>hascode-liferay-portlet-tutorial</keywords>
        </portlet-info>
        <security-role-ref>
            <role-name>administrator</role-name>
        </security-role-ref>
        <security-role-ref>
            <role-name>guest</role-name>
        </security-role-ref>
        <security-role-ref>
            <role-name>power-user</role-name>
        </security-role-ref>
        <security-role-ref>
            <role-name>user</role-name>
        </security-role-ref>
    </portlet>
    </portlet-app>
  • Because we didn’t have much to do here we’re going to declare some Liferay-specific portlet information.. first the liferay-display.xml we’re putting our portlet in the category ‘Samples’ .. we’re going to see what this means in the following screenshots of the Liferay user interface

    <?xml version="1.0"?>
    <!DOCTYPE display PUBLIC "-//Liferay//DTD Display 6.0.0//EN" "http://www.liferay.com/dtd/liferay-display_6_0_0.dtd">
    <display>
        <category name="category.sample">
            <portlet id="hascode-liferay-portlet"/>
        </category>
    </display>
  • If you like you may add some vendor and licence information by editing the liferay-plugin-package.properties

    name=hascode-liferay-portlet
    module-group-id=hasCode
    module-incremental-version=1
    tags=tutorial
    short-description=hasCode Portlets
    change-log=
    page-url=https://www.hascode.com
    author=Micha Kops
    licenses=LGPL
  • And finally editing the liferay-portlet.xml

    <?xml version="1.0"?>
    <!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.0.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_0_0.dtd">
    <liferay-portlet-app>
        <portlet>
            <portlet-name>hascode-liferay-portlet</portlet-name>
            <icon>/icon.png</icon>
            <instanceable>true</instanceable>
            <header-portlet-css>/css/main.css</header-portlet-css>
            <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
        </portlet>
        <role-mapper>
            <role-name>administrator</role-name>
            <role-link>Administrator</role-link>
        </role-mapper>
        <role-mapper>
            <role-name>guest</role-name>
            <role-link>Guest</role-link>
        </role-mapper>
        <role-mapper>
            <role-name>power-user</role-name>
            <role-link>Power User</role-link>
        </role-mapper>
        <role-mapper>
            <role-name>user</role-name>
            <role-link>User</role-link>
        </role-mapper>
    </liferay-portlet-app>
  • Please note that editing of the Liferay descriptors is optional and we won’t change anything there in the following examples!

  • Now build the portlet war file via IDE or

    mvn package

Installing and Adding the Portlet

Of course now that we have created a portlet we want to deploy it into our Liferay instance and add it to a page ..

  • First login to your Liferay instance with an account with sufficient permissions to deploy software .. if you’re using the bundled test instance try username/password test@liferay.com/test

    deployment step1 login
    Figure 1. Portlet Deployment Step 1 - Login
  • Select Manage > Control Panel from the top menu

    deployment step2 control panel
    Figure 2. Portlet Deployment Step 2 - Control Panel
  • Now go to Server > Plugins Installation > Install more Portlets > Upload File and upload your created war archive

    deployment step3 plugins
    Figure 3. Portlet Deployment Step 3 - Plugins Installation
    deployment step4 install portlet
    Figure 4. Portlet Deployment Step 4 - Install Portlet
    deployment step5 upload file
    Figure 5. Portlet Deployment Step 5 - Upload File
  • Now you should be able to see the following success message

    Portlet Deployment Success

    deployment success

  • Now click Back To Liferay > Add > More. Search for hascode or simply click on Sample (that’s the category that we’ve assigned to the portlet in the liferay-display.xml. Now you’re able to add the portlet to the screen using drag&drop or simply clicking Add

    deployment add portlet1
    Figure 6. Selecting the portlet from the control panel
    deployment add portlet2
    Figure 7. Adding the Portlet to the page

Inter-Portlet Communication

Now that we have written some portlets we want them to communicate ..

  • First we’re creating a Portlet to send a text from user input com.hascode.tutorial.portlet.EventSenderPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.portlet.ActionRequest;
    import javax.portlet.ActionResponse;
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.PortletURL;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    import javax.xml.namespace.QName;
    
    public class EventSenderPortlet extends GenericPortlet {
    	@Override
    	public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException {
    		final String message = req.getParameter("message");
    		if (message != null && !message.isEmpty()) {
    			res.setEvent(new QName("https://www.hascode.com/portlet", "message"), message);
    		}
    	}
    
    	@Override
    	public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException {
    		res.setContentType("text/html");
    		PrintWriter writer = res.getWriter();
    		PortletURL actionUrl = res.createActionURL();
    		final String output = String.format("<form action=\"%s\" method=\"post\"><label>Enter a message</label><input type=\"text\" name=\"message\"/><br/><input type=\"submit\"/></form>", actionUrl);
    		writer.println(output);
    	}
    }
  • Of course we need another portlet to receive the message and display it: com.hascode.tutorial.portlet.EventReceiverPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    
    import javax.portlet.EventRequest;
    import javax.portlet.EventResponse;
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    
    public class EventReceiverPortlet extends GenericPortlet {
    	@Override
    	public void processEvent(EventRequest req, EventResponse res) throws PortletException, IOException {
    		final String message = (String) req.getEvent().getValue();
    		res.setRenderParameter("message", message);
    	}
    
    	@Override
    	public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException {
    		final String message = req.getParameter("message");
    		res.getWriter().println("Message received from event: " + message);
    	}
    }
  • Finally we only need to declare the event, the two portlets and add a binding to the event for both portlets in the portlet.xml

    <?xml version="1.0"?>
    <portlet-app>
    	[..]
        <portlet>
            <portlet-name>hascode-event-sender-portlet</portlet-name>
            <display-name>hascode-event-sender-portlet</display-name>
            <portlet-class>com.hascode.tutorial.portlet.EventSenderPortlet</portlet-class>
            <expiration-cache>0</expiration-cache>
            <supports>
                <mime-type>text/html</mime-type>
                <portlet-mode>VIEW</portlet-mode>
            </supports>
            <portlet-info>
                <title>hascode-event-sender-portlet</title>
                <keywords>hascode-event-sender-portlet</keywords>
            </portlet-info>
            <supported-publishing-event>
                <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname>
            </supported-publishing-event>
        </portlet>
        <portlet>
            <portlet-name>hascode-event-receiver-portlet</portlet-name>
            <display-name>hascode-event-receiver-portlet</display-name>
            <portlet-class>com.hascode.tutorial.portlet.EventReceiverPortlet</portlet-class>
            <expiration-cache>0</expiration-cache>
            <supports>
                <mime-type>text/html</mime-type>
                <portlet-mode>VIEW</portlet-mode>
            </supports>
            <portlet-info>
                <title>hascode-event-receiver-portlet</title>
                <keywords>hascode-event-receiver-portlet</keywords>
            </portlet-info>
            <supported-processing-event>
                <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname>
            </supported-processing-event>
        </portlet>
        <event-definition>
            <qname xmlns:hc="https://www.hascode.com/portlet">hc:message</qname>
            <value-type>java.lang.String</value-type>
        </event-definition>
        [..]
    </portlet-app>

Transferring custom objects between portlets

Now we’re going to send a custom object as a message from one portlet to another.Be aware that there are some limitations for such an object: It must implement Serializable, must have a no-arg-constructor and be annotated with @XmlRootElement

  • First our custom message object com.hascode.tutorial.portlet.CustomPortletMessage

    package com.hascode.tutorial.portlet;
    
    import java.io.Serializable;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement
    public class CustomPortletMessage implements Serializable {
    	private static final long    serialVersionUID    = 1L;
    	private String                text;
    
    	public String getText() {
    		return text;
    	}
    
    	public void setText(String text) {
    		this.text = text;
    	}
    }
  • Now we need a portlet to create and send a message com.hascode.tutorial.portlet.AdvancedSenderPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.portlet.ActionRequest;
    import javax.portlet.ActionResponse;
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.PortletURL;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    import javax.xml.namespace.QName;
    
    public class AdvancedEventSenderPortlet extends GenericPortlet {
    	@Override
    	public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException {
    		final String text = req.getParameter("message");
    		if (text != null && !text.isEmpty()) {
    			CustomPortletMessage msg = new CustomPortletMessage();
    			msg.setText(text);
    			res.setEvent(new QName("https://www.hascode.com/portlet", "customMessage"), msg);
    		}
    	}
    
    	@Override
    	public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException {
    		res.setContentType("text/html");
    		PrintWriter writer = res.getWriter();
    		PortletURL actionUrl = res.createActionURL();
    		final String output = String.format("<form action=\"%s\" method=\"post\"><label>Enter a message</label><input type=\"text\" name=\"message\"/><br/><input type=\"submit\"/></form>", actionUrl);
    		writer.println(output);
    	}
    }
  • In the next step we’re creating the portlet to receive the message object and print its content com.hascode.tutorial.portlet.AdvancedReceiverPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    
    import javax.portlet.EventRequest;
    import javax.portlet.EventResponse;
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    
    public class AdvancedEventReceiverPortlet extends GenericPortlet {
    	@Override
    	public void processEvent(EventRequest req, EventResponse res) throws PortletException, IOException {
    		final CustomPortletMessage message = (CustomPortletMessage) req.getEvent().getValue();
    		res.setRenderParameter("message", message.getText());
    	}
    
    	@Override
    	public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException {
    		final String message = req.getParameter("message");
    		res.getWriter().println("Message received from event: " + message);
    	}
    }
  • Finally we need to declare the two portlets and the new event type in our portlet.xml

    <portlet>
    	<portlet-name>hascode-advanced-event-sender-portlet</portlet-name>
    	<display-name>hascode-advanced-event-sender-portlet</display-name>
    	<portlet-class>com.hascode.tutorial.portlet.AdvancedEventSenderPortlet</portlet-class>
    	<expiration-cache>0</expiration-cache>
    	<supports>
    		<mime-type>text/html</mime-type>
    		<portlet-mode>VIEW</portlet-mode>
    	</supports>
    	<portlet-info>
    		<title>hascode-advanced-event-sender-portlet</title>
    		<keywords>hascode-advanced-event-sender-portlet</keywords>
    	</portlet-info>
    
    	<supported-publishing-event>
    		<qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname>
    	</supported-publishing-event>
    </portlet>
    
    <portlet>
    	<portlet-name>hascode-advanced-event-receiver-portlet</portlet-name>
    	<display-name>hascode-advanced-event-receiver-portlet</display-name>
    	<portlet-class>com.hascode.tutorial.portlet.AdvancedEventReceiverPortlet</portlet-class>
    	<expiration-cache>0</expiration-cache>
    	<supports>
    		<mime-type>text/html</mime-type>
    		<portlet-mode>VIEW</portlet-mode>
    	</supports>
    	<portlet-info>
    		<title>hascode-advanced-event-receiver-portlet</title>
    		<keywords>hascode-advanced-event-receiver-portlet</keywords>
    	</portlet-info>
    
    	<supported-processing-event>
    		<qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname>
    	</supported-processing-event>
    </portlet>
    
    <event-definition>
    	<qname xmlns:hc="https://www.hascode.com/portlet">hc:customMessage</qname>
    	<value-type>com.hascode.tutorial.portlet.CustomPortletMessage</value-type>
    </event-definition>
  • Add the two portlets and send some messages – it should look like this

    Message Sender and Receiver Portlet Example

    message object event

Accessing the Portlet configuration

Our next portlet is going to read some configuration values from the portlet descriptor file and display it ..

  • First we’re creating another portlet com.hascode.tutorial.portlet.SimplePreferencesReadingPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletException;
    import javax.portlet.PortletPreferences;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    
    public class SimplePreferencesReadingPortlet extends GenericPortlet {
    	@Override
    	public void render(RenderRequest req, RenderResponse res) throws PortletException, IOException {
    		res.setContentType("text/html");
    		PrintWriter writer = res.getWriter();
    
    		PortletPreferences prefs = req.getPreferences();
    		String[] tags = prefs.getValues("tags", null);
    		writer.println("<b>Tags:</b><br/><ul>");
    		for (String tag : tags) {
    			writer.println("<li>" + tag + "</li>");
    		}
    		writer.println("</ul>");
    	}
    }
  • Finally we’re declaring the portlet in the portlet.xml and add some portlet-preferences, too

    <portlet>
        <portlet-name>hascode-preferences-portlet</portlet-name>
        <display-name>hascode-preferences-portlet</display-name>
        <portlet-class>com.hascode.tutorial.portlet.SimplePreferencesReadingPortlet</portlet-class>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <portlet-info>
            <title>hascode-preferences-portlet</title>
            <keywords>hascode-preferences-portlet</keywords>
        </portlet-info>
        <portlet-preferences>
            <preference>
                <name>tags</name>
                <value>portlet</value>
                <value>tutorial</value>
                <value>liferay</value>
                <value>java</value>
            </preference>
        </portlet-preferences>
    </portlet>
  • Finally our portlet looks like this and displays the keywords from the portlet configuration

    portlet preferences
    Figure 8. Reading Portlet Preferences

Using Annotations

Since Portlet Specification 2.0 it is possible to use annotations to create portlet applications:

  • @RenderMode: Marks a method for execution for a specified render mode. The mode is defined using the annotation’s name flag.. e.g. @RenderMode(name = “view”)

  • @ProcessAction: Marks a method to be executed in a named action request

  • Let’s create a running example .. the following portlet offers a link, when the link is clicked, an action is triggered and the current time is displayed: com.hascode.tutorial.portlet.SimpleAnnotatedPortlet

    package com.hascode.tutorial.portlet;
    
    import java.io.IOException;
    import java.util.Date;
    
    import javax.portlet.ActionRequest;
    import javax.portlet.ActionResponse;
    import javax.portlet.GenericPortlet;
    import javax.portlet.PortletURL;
    import javax.portlet.ProcessAction;
    import javax.portlet.RenderMode;
    import javax.portlet.RenderRequest;
    import javax.portlet.RenderResponse;
    
    public class SimpleAnnotatedPortlet extends GenericPortlet {
    
    	@RenderMode(name = "view")
    	public void showMeSomeText(RenderRequest req, RenderResponse res) throws IOException {
    		String theTime = req.getParameter("thetime");
    		PortletURL actionUrl = res.createActionURL();
    		actionUrl.setParameter(ActionRequest.ACTION_NAME, "mySpecialAction");
    		res.getWriter().println("The time is: " + theTime + "<br/><small><a href=\"" + actionUrl + "\">Update time</a></small>");
    	}
    
    	@ProcessAction(name = "mySpecialAction")
    	public void timeLookup(ActionRequest req, ActionResponse res) {
    		res.setRenderParameter("thetime", new Date().toString());
    	}
    }
  • Our portlet looks like this now

    Annotation Based Portlet Example

    annotation example

JSF Portlet Bridge and Java Server Faces 2

Finally we’re going to get Java Server Faces running as a Portlet. This isn’t as easy as it sound because the JSF lifecycle contains much more phases than the Portlet lifecycle.

That’s why the JSF Portlet Bridges were invented and specified by the Java Community Process in JSR-301 (Portlet Bridge 1.0) and JSR-329 (Portlet Bridge 2.0). Please note that JSF’s target version for both bridges is 1.2 – afaik there is no JSR for a Portlet Bridge for JSF 2.x, please correct me here if I am wrong.

There are several vendors filling this gap – the one I chose here was Portletfaces that supports bridging between Portlet and JSF 2 lifecycle. If you want to take a deeper look into Portletfaces features, please consults its detailed documentation.

With this information and Portletfaces on board we’re going to create a JSF Portlet that displays a sorted list of strings from a ManagedBean. In addition we’re going to map the Portlet’s action modes EDIT, VIEW and HELP to a corresponding JSF instance so that the user is able to change the way the strings are sorted using the Portlet’s configuration mode.

  • First we need to add some dependencies and repositories for Java Server Faces and Portletfaces to our pom.xml

    <dependency>
    	<groupId>org.portletfaces</groupId>
    	<artifactId>portletfaces-bridge</artifactId>
    	<version>2.0.0</version>
    </dependency>
    <dependency>
    	<groupId>javax.el</groupId>
    	<artifactId>el-api</artifactId>
    	<version>1.0</version>
    	<scope>provided</scope>
    </dependency>
    <dependency>
    	<groupId>com.sun.faces</groupId>
    	<artifactId>jsf-api</artifactId>
    	<version>2.1.1-b03</version>
    </dependency>
    <dependency>
    	<groupId>com.sun.faces</groupId>
    	<artifactId>jsf-impl</artifactId>
    	<version>2.1.1-b03</version>
    </dependency>
    
    <repositories>
    	<repository>
    		<id>maven2-repository.dev.java.net</id>
    		<url>http://download.java.net/maven/2</url>
    	</repository>
    	<repository>
    		<id>maven2-repository-portletfaces.org</id>
    		<url>http://repo.portletfaces.org/mvn/maven2</url>
    	</repository>
    </repositories>
  • In the next step we’re declaring a ManagedBean using the @ManagedBean annotation in com.hascode.tutorial.jsf.UserBean

    package com.hascode.tutorial.jsf;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.SessionScoped;
    import javax.faces.context.ExternalContext;
    import javax.faces.context.FacesContext;
    import javax.portlet.ActionResponse;
    import javax.portlet.PortletMode;
    import javax.portlet.PortletModeException;
    
    @ManagedBean
    @SessionScoped
    public class UserBean {
    	private String                sortType    = "normal";
    
    	private final List<String>    names        = new ArrayList<String>();
    	{
    		names.add("adam");
    		names.add("barry");
    		names.add("douglas");
    		names.add("ethan");
    	}
    
    	public List<String> getUserNames() {
    		if ("reverse".equals(sortType)) {
    		Collections.reverse(names);
    			return names;
    		}
    
    		Collections.sort(names);
    		return names;
    	}
    
    	public void save() throws PortletModeException {
    		// Switch the portlet mode back to VIEW.
    		FacesContext facesContext = FacesContext.getCurrentInstance();
    		ExternalContext externalContext = facesContext.getExternalContext();
    		ActionResponse actionResponse = (ActionResponse) externalContext.getResponse();
    		actionResponse.setPortletMode(PortletMode.VIEW);
    	}
    
    	public String getSortType() {
    		return sortType;
    	}
    
    	public void setSortType(String sortType) {
    		this.sortType = sortType;
    	}
    }
  • Now we need to get the FacesServlet Dispatcher running by adding the following web.xml in src/main/webapp/WEB-INF

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    </web-app>
  • Now we need some Facelets for the different modes EDIT, HELP and VIEW .. let’s begin view the template for the view mode by creating a file named viewMode.xhtml in src/main/webapp/xhtml. We don’t do much here – we’re fetching the usernames from the ManagedBeans, iterate over them and output them in a html list

    <?xml version="1.0" encoding="UTF-8"?>
    <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:portlet="http://java.sun.com/portlet_2_0">
    <h:head/>
    <h:body>
        <h:outputText value="VIEW"/>
        <ul>
            <ui:repeat value="${userBean.userNames}" var="userName">
                <li>
                    <h:outputText value="${userName}"/>
                </li>
            </ui:repeat>
        </ul>
    </h:body>
    </f:view>
  • Next our template for the help mode .. same directory as above but saved as helpMode.xhtml. We’re not helpful here and just output “HELP” :)

    <?xml version="1.0" encoding="UTF-8"?>
    <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
    <h:head/>
    <h:body>
        <h:outputText value="HELP"/>
    </h:body>
    </f:view>
  • And finally the template to edit the settings editMode.xhtml. The user is able to select his desired sort mode in a select box.

    <?xml version="1.0" encoding="UTF-8"?>
    <f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
    <h:head/>
    <h:body>
        <h:outputText value="EDIT"/>
        <h:form>
            <h:messages globalOnly="true"/>
            <h:selectOneMenu id="selectSortType" value="#{userBean.sortType}">
                <f:selectItem id="sort-normal" itemLabel="Normal Sorted" itemValue="normal"/>
                <f:selectItem id="sort-reverse" itemLabel="Reverse Sorted" itemValue="reverse"/>
            </h:selectOneMenu>
            <hr/>
            <h:commandButton actionListener="#{userBean.save}" value="Submit"/>
        </h:form>
    </h:body>
    </f:view>
  • Having created our facelets we’re going to define a classical JSF navigation by creating the following faces-config.xml in src/main/webapp/WEB-INF. It simply says that an outcome success from the editMode leads to the viewMode.

    <?xml version="1.0" encoding="UTF-8"?>
    <faces-config 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-facesconfig_2_0.xsd" version="2.0">
    <navigation-rule>
        <from-view-id>/xhtml/editMode.xhtml</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/xhtml/viewMode.xhtml</to-view-id>
        </navigation-case>
    </navigation-rule>
    </faces-config>
  • Out JSF fragments do look fine now so it’s time that we arrange a date with the Portlet API using the Portlet bridge. The following declaration added to your portlet.xml enables the EDIT,VIEW,HELP modes and maps them to the corresponding JSF actions and uses org.portletfaces.bridge.GenericFacesPortlet as portlet bridge and mapper between portlet requests and jsf lifecycle.

    <?xml version="1.0"?>
    <portlet>
        <portlet-name>1</portlet-name>
        <display-name>JSF2-Portlet-Bridge</display-name>
        <portlet-class>org.portletfaces.bridge.GenericFacesPortlet</portlet-class>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.view</name>
            <value>/xhtml/viewMode.xhtml</value>
        </init-param>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.edit</name>
            <value>/xhtml/editMode.xhtml</value>
        </init-param>
        <init-param>
            <name>javax.portlet.faces.defaultViewId.help</name>
            <value>/xhtml/helpMode.xhtml</value>
        </init-param>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
            <portlet-mode>EDIT</portlet-mode>
            <portlet-mode>HELP</portlet-mode>
        </supports>
        <portlet-info>
            <title>hasCode JSF2-Portlet-Bridge</title>
            <short-title>hasCode JSF2-Portlet-Bridge</short-title>
            <keywords>hasCode JSF2-Portlet-Bridge</keywords>
        </portlet-info>
    </portlet>
  • That’s what our portlet looks like in full action

    jsf component step1
    Figure 9. JSF Portlet Component Step 1
    jsf component step2
    Figure 10. JSF Portlet Component Step 2
    jsf component step3
    Figure 11. JSF Portlet Component Step 3
    jsf component step4
    Figure 12. JSF Portlet Component Step 4

Portlet Example Screencast

The following screencast on YouTube demonstrates the usage of the portlets implemented above

Tutorial Sources

I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:

git clone https://github.com/hascode/liferay-portlet-tutorial.git

Additional Information: Java Server Faces

If you’re interested in additional information about Java Server Faces, please feel free to have a look at my other JSF articles on this blog.

Article Updates

  • 2018-06-01: Embedded YouTube video removed (GDPR/DSGVO).

  • 2015-04-01: Code formatting fixed, image captions added, links to my JSF articles added.