Sharing Our Passion for Technology
& continuous learning
〈  Back to Blog

Portlet Development using JSF, PrimeFaces and Spring

This article presents techniques on how to develop Java Portlets using JavaServer Faces, PrimeFaces and Spring. This hands-on example will integrate all of these technologies into a single application.

Assumptions

This post assumes that you have a basic understanding of Portlet, JSF, PrimeFaces and Spring. And a good understanding of Java 5 and annotations.

Resources

Before getting started, please have the following downloaded and setup in your environment.

Development Steps

  • Create a Web Project named SimplePortlet and convert it to Maven Project / enable Maven support.
  • Let's start with the application requirements. So, build your Maven Configurations - pom.xml file. As you might have guessed, this will contain the dependencies for the following. If you have confusion about any of the dependencies below, don't worry, things will be clear as we move forward.
    • Spring Web
    ``` org.springframework spring-web ${spring.version} ```
    • Portlet
    ``` javax.portlet portlet-api ${portlet-api.version} provided ```
    • Servlet
    ``` javax.servlet servlet-api ${servlet-api.version} provided javax.servlet jstl 1.2 compile ```
    • JEE Dependency Injection
    ``` javax.inject javax.inject 1 ```
    • JSF
    ``` com.sun.faces jsf-api 2.1.6 com.sun.faces jsf-impl 2.1.6 ```
    • EL
    ``` com.sun.el el-ri 1.0 javax.el el-api 1.0 provided ```
    • PrimeFaces
    ``` org.primefaces primefaces 3.0 ```
    • PortletFaces bridge
    ``` org.portletfaces portletfaces-bridge 2.0.1 org.portletfaces.alloy.faces alloyfaces 1.0.1.1 commons-fileupload commons-fileupload 1.2.2 commons-io commons-io 1.3.1 . ```
  • Add the following repositories to your pom.xml so that all the dependencies can be downloaded successfully.
  • ``` maven2-repository.dev.java.net http://download.java.net/maven/2 maven2-repository.jboss.org http://repository.jboss.org/nexus/content/groups/public-jboss maven2-repository-portletfaces.org http://repository.portletfaces.org/content/repositories/portletfaces-releases prime-repo PrimeFaces Maven Repository http://repository.primefaces.org default ```
    • Add the the following properties to your pom.xml file.
    ``` 2.0.3 2.0 2.5 2.0 4.10 3.1.1.RELEASE ```
  • Build your web.xml file
    • Add Spring bootstrap - ContextLoaderListener.
    • Add RequestContextListener to the web.xml to enable Spring support request and session scoped beans.
    • Add JSF Servlet Configurations.
    • Finally, the web.xml looks like this:
    ``` SimplePortlet contextConfigLocation classpath:applicationContext.xml javax.faces.CONFIG_FILES /WEB-INF/faces-config.xml javax.faces.PROJECT_STAGE Development javax.faces.PARTIAL_STATE_SAVING false org.springframework.web.context.ContextLoaderListener org.springframework.web.context.request.RequestContextListener Faces Servlet javax.faces.webapp.FacesServlet 1 Facelet View XHTML *.xhtml nobody nobody ```
    • Create faces-config.xml file and add SpringBeanFacesELResolver into it. JSF Els look up Spring beans with the help of this EL Resolver. This looks like:
    ``` org.springframework.web.jsf.el.SpringBeanFacesELResolver ```
    • Write your JSF Controller class. In this example, I've created a controller class that has a zipCode field which is submitted from the user request. This controller has dependency with the service layer to get the weather information which has been injected into this. Controller invokes the service class, WeatherServiceClient in this case, to get weather info for that zip code and gets it back as a WeatherReturn. (The controller also has a field called currentTime. The view invokes this using AJAX and displays the current time. This is done using PrimeFaces poll component)
    ``` /** * */ package com.sourceallies.portlets.spring.controller; import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Named; import com.sourceallies.portlets.spring.bean.WeatherReturn; import com.sourceallies.service.WeatherServiceClient; /** * @author lsah * */ @Named("viewController") public class WeatherViewController implements Serializable { /** * */ private static final long serialVersionUID = 8811407513884269900L; private static final Logger LOGGER = Logger.getLogger(WeatherViewController.class.getName()); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a z"); @Inject @Named("weatherServiceClient") private WeatherServiceClient weatherServiceClient; private WeatherReturn weatherReturn; private String zipCode = "50312"; private String currentTime = DATE_FORMAT.format(new Date()); /** * @return the currentTime */ public String getCurrentTime() { return currentTime; } /** * @param currentTime * the currentTime to set */ public void setCurrentTime(String currentTime) { this.currentTime = currentTime; } public WeatherViewController() { } public String updateWeather() { LOGGER.info("Zip Code: " + zipCode); weatherReturn = weatherServiceClient.getWeatherByZipCode(zipCode); return null; } public void updateTime() { // LOGGER.info("------------------>>>>>>Updating time..."); this.currentTime = DATE_FORMAT.format(new Date()); } /** * @return the weatherServiceClient */ public WeatherServiceClient getWeatherServiceClient() { return weatherServiceClient; } /** * @param weatherServiceClient * the weatherServiceClient to set */ public void setWeatherServiceClient(WeatherServiceClient weatherServiceClient) { this.weatherServiceClient = weatherServiceClient; } /** * @return the weatherReturn */ public WeatherReturn getWeatherReturn() { if (this.weatherReturn == null) { weatherReturn = weatherServiceClient.getWeatherByZipCode("50312"); LOGGER.info("Temperature (Spring Portlet MVC): " + weatherReturn.getTemperature()); } return weatherReturn; } /** * @param weatherReturn * the weatherReturn to set */ public void setWeatherReturn(WeatherReturn weatherReturn) { this.weatherReturn = weatherReturn; } /** * @return the zipCode */ public String getZipCode() { return zipCode; } /** * @param zipCode * the zipCode to set */ public void setZipCode(String zipCode) { this.zipCode = zipCode; } } ```
    • The WeatherReturn bean is shown below.
    ``` /** * Weather.java * Feb 28, 2012 */ package com.sourceallies.portlets.spring.bean; import java.io.Serializable; /** * @author Lal * */ public class WeatherReturn implements Serializable { /** * */ private static final long serialVersionUID = 2221936547464343855L; private String zipCode; private String temperature; private String city; private String state; private String description; /** * @return the zipCode */ public String getZipCode() { return zipCode; } /** * @param zipCode * the zipCode to set */ public void setZipCode(String zipCode) { this.zipCode = zipCode; } /** * @return the temperature */ public String getTemperature() { return temperature; } /** * @param temperature * the temperature to set */ public void setTemperature(String temperature) { this.temperature = temperature; } /** * @return the city */ public String getCity() { return city; } /** * @param city * the city to set */ public void setCity(String city) { this.city = city; } /** * @return the state */ public String getState() { return state; } /** * @param state * the state to set */ public void setState(String state) { this.state = state; } /** * @return the description */ public String getDescription() { return description; } /** * @param description * the description to set */ public void setDescription(String description) { this.description = description; } /** * @param zipCode * @return */ public WeatherReturn withZipCode(String zipCode) { this.zipCode = zipCode; return this; } /** * @param temperature * @return */ public WeatherReturn withTemperature(String temperature) { this.temperature = temperature; return this; } /** * @param city * @return */ public WeatherReturn withCity(String city) { this.city = city; return this; } /** * @param state * @return */ public WeatherReturn withState(String state) { this.state = state; return this; } /** * @param description * @return */ public WeatherReturn withDescription(String description) { this.description = description; return this; } } ```
  • Write your service layer. I've simply short-circuited the service layer for example purpose ONLY. WeatherServiceClient in this case simply returns an object with static / hard-coded values as shown below. A real-time service client would invoke a real-time weather web service and get the data from there - which is beyond the scope of this article.
  • ``` /** * */ package com.sourceallies.service; import java.util.logging.Logger; import javax.inject.Named; import com.sourceallies.portlets.spring.bean.WeatherReturn; /** * @author lsah * */ @Named(value = "weatherServiceClient") public class WeatherServiceClient { private static final Logger LOGGER = Logger.getLogger(WeatherServiceClient.class.getName()); public WeatherServiceClient() { } /** * @param zipCode * @return the weather for the given zip code. */ public WeatherReturn getWeatherByZipCode(String zipCode) { LOGGER.info("Zip Code: " + zipCode); // Invoke Web Service / DAO layer to get the real-time data and return return new WeatherReturn().withZipCode(zipCode).withCity("Des Moines").withState("IA").withTemperature("37").withDescription("Light Rain"); } } ```
    • This WeatherServiceClient mentioned above is named as "weatherServiceClient". This was injected into the controller class using @Inject and @Named annotations.
    • Write your Spring Bean configuration file. I've named it applicationContext.xml in the web.xml contextConfigLocation context parameter. You can choose any name as long as you have the same name in the web.xml above. We have all the beans configured using annotations so this file doesn't have any bean creation setup. Instead it tells Spring the meta information about the annotation processing i.e. enable annotation processing and the packages that contain the annotated beans to be processed via annotation processing.
    ``` ```
    • Now let's create the views. As you might have guessed, we are going to use Facelets on views. Since a Portlet operates in three modes namely VIEW, EDIT and HELP and we are going to create a portlet to support all three modes, we will create three view pages naming them view.xhtml, edit.xhtml and help.xhtml. Create a folder named xhtmls inside WEB-INF to place these files. Let's start with view.xhtml.
    • This contains a JSF form with a singular field zipCode. This portlet will query weather for the entered zip code. It displays the weather info in the same page. Even though, you can navigate to any view page using JSF navigation based on your need. Here's our view.
    ```

    Check Weather

    Please enter your Zip Code below to view weather update:

    Zip Code:

    Weather at Zip Code: #{viewController.zipCode}

    Location:
    Temperature:
    Description:
    ```
    • I created the exact same page for EDIT mode just to have EDIT mode support - just copy and paste the same content in a file named edit.xhtml. However, you can create your own based on what you need for your application.
    • I've created a simplest page for HELP mode for this example.
    ``` HELP MODE ```
  • Now that we have created almost everything, let's create the Portlet configuration file / deployment descriptor. As mentioned before, we are using PortletFaces bridge. So, we are going to use org.portletfaces.bridge.GenericFacesPortlet as our portlet class, the class provided by PortletFaces library to handle portlet lifecycles. This class along with the PortletFaces library is responsible for bridging the gap between portlet and JSF servlet lifecycles and handles JSF views as portlets on the Portal page. Here is the Portlet configurations file:
  • ``` Weather Portlet JSF PortletFaces Bridge weatherPortlet Weather Portlet using JSF PortletFaces Bridge org.portletfaces.bridge.GenericFacesPortlet javax.portlet.faces.defaultViewId.view /WEB-INF/xhtmls/view.xhtml javax.portlet.faces.defaultViewId.edit /WEB-INF/xhtmls/edit.xhtml javax.portlet.faces.defaultViewId.help /WEB-INF/xhtmls/help.xhtml text/html VIEW HELP EDIT en Weather Portlet (PortletFaces) Title WeatherPortletPortletFacesTitle weather portlet faces portlet ```
  • Here is the snapshot of my Eclipse project assembly.
  • Simple Portlet Project Assembly
    • This completes our development of sample Weather portlet. Now let's deploy this portlet.

    Deployment

    Now that we are done with the development, let's deploy this portlet on Apache Pluto Portal Server. I am using Pluto 2.0.3.
    • Create Tomcat Context Deployment Descriptor. This is an XML file named same as the project name and is placed in the webapp/META-INF/ directory. Our SimplePortlet.xml contains the following information:
    • ``` ```
    • Add the following plugins to yout pom.xml. The first is the compiler plugin that tells maven which version of java to use for compile java classes. The second and third are maven war and pluto plugins which tell maven how to create WAR assembly and where to put the deployment descriptors in the assembly so that Pluto can locate the deployment configurations and deploy the portlet. The fourth and last one is the automatic deployment plugin that tells maven where to copy the final deployment package. We are copying to pluto webapps directory so that it will be automatically deployed.
    • ``` ${pom.name} org.apache.maven.plugins maven-compiler-plugin 1.6</source> 1.6 maven-war-plugin ${project.build.directory}/pluto-resources/web.xml org.apache.portals.pluto maven-pluto-plugin ${pluto.version} generate-resources assemble maven-antrun-plugin integration-test run ```
    • Add a property catalina.home to your pom.xml file. Change its value to point to the home directory of your Pluto installation directory.
    • ``` /home/lsah/java/pluto-2.0.3 ```
    • The resulting pom.xml looks like this.
    • ``` 4.0.0 com.sourceallies.simpleportlet SimplePortlet 0.1 war ${pom.artifactId} 2.0.3 2.0 2.5 2.0 4.10 3.1.1.RELEASE /home/lsah/java/pluto-2.0.3 org.springframework spring-web ${spring.version} javax.portlet portlet-api ${portlet-api.version} provided javax.servlet servlet-api ${servlet-api.version} provided javax.servlet jstl 1.2 compile javax.inject javax.inject 1 com.sun.faces jsf-api 2.1.6 com.sun.faces jsf-impl 2.1.6 com.sun.el el-ri 1.0 javax.el el-api 1.0 provided org.primefaces primefaces 3.0 org.portletfaces portletfaces-bridge 2.0.1 org.portletfaces.alloy.faces alloyfaces 1.0.1.1 commons-fileupload commons-fileupload 1.2.2 commons-io commons-io 1.3.1 junit junit ${junit.version} test ${pom.name} org.apache.maven.plugins maven-compiler-plugin 1.6</source> 1.6 maven-war-plugin ${project.build.directory}/pluto-resources/web.xml org.apache.portals.pluto maven-pluto-plugin ${pluto.version} generate-resources assemble maven-antrun-plugin integration-test run org.eclipse.m2e lifecycle-mapping 1.0.0 org.apache.portals.pluto maven-pluto-plugin [2.0.3,) assemble maven2-repository.dev.java.net http://download.java.net/maven/2 maven2-repository.jboss.org http://repository.jboss.org/nexus/content/groups/public-jboss maven2-repository-portletfaces.org http://repository.portletfaces.org/content/repositories/portletfaces-releases prime-repo PrimeFaces Maven Repository http://repository.primefaces.org default ```
      • Run the maven command mvn install to have the build war file automatically deployed to your Pluto portal server. You can also run this with Eclipse IDE – just select Run As → Maven Install
      • Now if you go to your Pluto Admin page, you will see your Portlet deployed there. You can simply create a new Portal page with a name of your choice and add this Portlet on it or add this Portlet on an existing Portal page to view / edit / help with the portlet.
      <img src="/img/posts/portal-admin1.png" width="1127" height="400"class="img-responsive"> weatherPortlet shown on the Pluto Admin Page <img src="/img/posts/posts/portal-page.png" width="1110" height="332"class="img-responsive"> weatherPortlet added on Source Allies Weather Portal
〈  Back to Blog