Step1: From DB to simple UI

Pet Catalog (or PetStore) is a most popular "HelloWorld" for J2EE technologies. Many tutorials, explained some new technology use it for demonstrating advantages, promised by this technology. Many of us started to study J2EE from these examples. Last of them readed by me was about PetStore on JSF 2.0 & EJB 3.1 in J2EE 6.0 (http://weblogs.java.net/blog/caroljmcdonald/archive/2009/08/java_ee_6_pet_c.html)
But, there are no such example in Liferay. Liferay site said to use WOL (World Of Liferay) as such PetStore, but, these portlets (even they implemented some very useful functionality) not so easy to people just come into Liferay portlets development to study how to do it).
So, I've decided to write same example, but as Liferay Portlet

What we are going to do

During developing Pet Catalog for Liferay we will:

  • Install Liferay and Liferay Plugins SDK;
  • Create Simple Project;
  • Configure Eclipse to use it (it is not necessary and any other IDE may be used);
  • Discuss technologies used inside Liferay;
  • Developing DB entities & Services with using Service Builder;
  • Developing UI with using JSPPortlet.

Some Liferay specific UI features (like tags to implement tables with paginator) will be discussed also.
Not so much - but it is only first tutorial and I hope (if it will be interesting) to continue this series with more tutorials explained basic Liferay functionality and services.

All sources for this step available at SVN: https://emforge.svn.sourceforge.net/svnroot/emforge/experiments/petcatalog-portlet/step1/petcatalog-portlet/ or you can browse them via Project Sources

Install Liferay and Plugins SDK

Simplest way to start using liferay - is download Liferay version bundled with some server - for example tomcat 6.0 (default configuration) from SourceForge site (https://sourceforge.net/projects/lportal/files/Liferay%20Portal/5.2.3/liferay-portal-tomcat-6.0-5.2.3.zip/download). After download unzip it somewhere (lets say <path_to_liferay>), go to tomcat bin directory (<path_to_liferay>/tomcat-6.0.18/bin) and run tomcat (./startup.sh)
Liferay ships with tomcat preconfigured to run liferay, so, all required memory settings, as well as preinstalled applications are ready to use - and simple run should work (ok, in my case it is working in 99%). To be sure liferay started well - you need to check logs (<path_to_liferay>/tomcat-6.0.18/log/catalina.out). After about 3-5 minutes - server should be run and ready to use.
To validate it - navigate your browser to http://localhost:8080 - you should see default liferay page with 7Coz demo. To login as Admin simple click on "Login as Bruno Admin" link
There are different ways to develop portlets for Liferay - more details you can read in liferay wiki (http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/How+do+you+develop+-+Development+Strategies). We will use Plugin SDK Environment. To develop portlets in Plugins Environment we need to download Plugins SDK (again, from SourceForge: https://sourceforge.net/projects/lportal/files/Liferay%20Portal/5.2.3/liferay-plugins-sdk-5.2.3.zip/download), unzip it somewhere (<path_to_plugins>) and edit <path_to_plugins>/build.properties to point correct portal installation (uncommented property app.server.dir should point to <path_to_liferay> in our case).

Create Portlet Project

To start new portlet project switch to <path_to_plugins>/portlets and call
 

# ant -Dportlet.name=petcatalog -Dportlet.display.name="Pet Catalog" create

(I hope you have ant and java installed!)
As result, it will create folder petcatalog-portlet with required project structure and some initial files

What we have inside

Inside we have:

  • build.xml - is an ant-file to build/deploy our project. Please, note - since this file is referenced to ant-files from plugins-sdk - this project cannot be moved somethere out of plugins-sdk;
  • docroot - root of portlet - actually it is root of web-application;
  • docroot/WEB-INF - standard folder with configuration files;
  • docroot/WEB-INF/portlet.xml & docroot/WEB-INF/liferay-portlet.xml - description of portlets implemented in plugin;
  • docroot/WEB-INF/liferay-display.xml - file defined how this portlet displayed in applications menu in liferay;
  • docroot/WEB-INF/liferay-plugin-package.properties - file contains some packaging options for the project;
  • docroot/WEB-INF/src - java-sources;

Configure Eclipse Project

This step is not required - actually you can do everything described later in any IDE and command-line, but I've used to use Eclipse and feel myself much more comfortable in it. So, we will setup eclipse project to work on portlet. To do it switch into portlet folder (<path_to_liferay>/portlets/petcatalog-portlet) and call
 

# ant setup-eclipse

This command will create required Eclipse project files.
Before we will import generated project into Eclipse workspace, we need to get portal sources. It is not required fro command-line build, but project generated has some dependencies to this portal project, so, we need to have it in workspace. Simple import portal project from SVN http://svn.liferay.com/repos/public/portal/tags/5.2.3/ into workspace (please not - it will take some - quite long - time since project quite big)
Now we can import generated portlet project into workspace also

Configure Tomcat server under Eclipse

Now, to be able to run and debug portal (and our portlet) under Eclipse we need configure Liferay's tomcat server under Eclipse:

  1. Define Tomcat 6.0 Runtime (Window -> Preferences -> Server -> Runtime Environment -> Add -> Apache Tomcat 6.0) and point it to tomcat in Liferay (<path_to_liferay>/tomcat-6.0.18)
  2. Create Liferay server instance (Services Tab -> New -> Server)
  3. Configure it (double click on created server):
    1. Increase Timeouts to something big enough;
    2. Server Locations -> Switch to "Use Tomcat Installation";
    3. Set standard Liferay Java options: Open Launch Configuration -> Arguments Tab -> VM Arguments -> Add -Xms128m -Xmx1024m -XX:MaxPermSize=128m -Dfile.encoding=UTF8 -Duser.timezone=GMT
    4. Add sources of projects (to make it possible to debug): Open Launch Configuration -> Source Tab -> Add -> Java Project -> Select both - portal & portlet projects


Now, you should be able to start Liferay in Eclipse and see it's log file in Console view.
latest step to setup ant-target. Open Ant View (Window -> Show View -> Ant) and drag&drop build.xml from portlet project into this view. Now, you will able to call required target (like deploy) for our portlet by double-click on it in this view

What Technologies will be used in Portlet Development

OK, now we are ready to start for real development, but before lets shortly list of technologies Liferay used or we will use.
For working with Database Liferay used Hibernate. In our cae we will not have care about hibernate mappings and configuration, since everything is done by Liferay core, or will be done for us by ServiceBuilder.
Spring Framework widelly used in Liferay to link different components, but again - we do not need care about it too much - since all required bean configuration will be done for us by ServiceBuilder
There are different frameworks available to use for Portlets UI development: Struts, Spring MVC, JSF (including IceFaces or RichFaces) and many more, but, in our case we will use JSP. Yes, yes, it is 2009 year - but in some cases it is still make sense to use plain, old, good JSP in some project. In our case reasons are:

  1. Each portlet (including our own) implemented not a big system, bug small part of functionality. And it is responsibility of Portal (Liferay) to combine them into bigger system;
  2. We do not need templating (like Tiles in Struts or Facelets in JSF) - since templates usually used to define "general" page look: header, footer, borders, but in our case it is responsibility of Liferay - we only need care about our small part of screen;
  3. With using JSP we can use some very useful tag (like to implement table with paginator);

So, be ready to remember your development childhood :)

Working with ServiceBuilder

ServiceBuilder is a special tool provided by Liferay to build database entities, and services working with them. Speaking in EJB terms it help us to build @Entity and @Stateless beans.
In our example I will use "simplified" version of catalog - same as used in this JSF 2.0 and EJB 3.0 sample - supported only 1 entity object to edit/display list of pets.
So, to define our entity lets create file docroot/WEB-INF/service.xml with content:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 5.1.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_5_1_0.dtd">
<service-builder package-path='petcatalog'>
    <namespace>PetCatalog</namespace>

    <!-- Project -->
    <entity name='Item' table='PETS_ITEM' local-service='true' remote-service='false'>
        <!-- PK fields -->
        <column name='itemId' type='long' primary='true'/>
        
        <column name="productId" type="String" />        
        <column name="name" type="String" />        
        <column name="description" type="String" />        
        <column name="imageUrl" type="String" />        
        <column name="imageThumbUrl" type="String" />        
        <column name="price" type="double" />
    </entity>
</service-builder>

This file described entities (and not only entities - but we will speak about it later) operating by our portlet. In our case we simple defined same fields as used in JSF2/EJB3.1 PetCatalog sample.
Not, we can generate services classes and files from this description - simple run target build-service in our portlet ant-file. As result of this target followed classes will be generated:

  • Model & Service Implementation classes;
  • Model & Service Interface & Util classes;
  • Hibernate mapping, Model Hints, Spring Configuration files.

Lets look more details into them

Model and Service Implementation classes

You can find them in docroot/WEB-INF/src/petcatalog folder (petcatalog package as it was defined in our service.xml). Model - is a DB-Model class (same as @Entity in EJB3), Service - is a service class - same as @Statelles in EJB 3.
For both, Model and Service we may see 3 different kind of classes:

  • Base Classes (petcatalog.model.impl.ItemModelImpl & petcatalog.service.base.ItemLocalServiceBaseImpl) - this classes always generated by ServiceBuilder and implemented basic functionality - like required accessors for attributes, basic CRUD operations and so on. Since this classes always regenerated by ServiceBuilder - we should not change them;
  • Implementation Classes (petcatalog.model.impl.ItemImpl & petcatalog.service.impl.ItemLocalServiceImpl) - they are inherited from base-classes and used for adding additional logic (methods, attributes not covered by service.xml file) by user. So, if we will like to add some new method into your service - we will add it into petcatalog.service.impl.ItemLocalServiceImpl)
  • Interfaces - will be discussed in next topic, but - important thing is: interfaces are always generated from implementation. So, after you will add some extra method into your service implementation class - you will need to call ServiceBuilder again to regenerate interfaces

Model and Service Interface and Util classes

Will classes placed in docroot/WEB-INF/service folder and compiled into docroot/WEB-INF/lib/petclinic-service.jar. Improtant thing - these classes always regenerated by service build, so, it does not make sense to do any changes there - instead you should change Implementation clases discussed before.
Util-class (petcatalog.service.ItemLocalServiceUtil) implemented singleton pattern - it is used to access Service Implementation instance by using static methods.
In rest portlet code we should use this Util class to access Service functionality, and use Model Interface to operate with models (then real implementation is not required).

Hibernate mapping, Model Hints, Spring Configuration files

Additionally ServiceBuilder generated:

  • Hibernate mapping file for our portlet (at docroot/WEB-INF/src/META-INF/portlet-hbm.xml);
  • Model Hints file (at docroot/WEB-INF/src/META-INF/portlet-model-hints.xml) - this file contains additional information about model, helped Liferay to build UI - like length of fileds, format and many more (read Liferay wiki for details: http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Customize+DB+Column+Sizes);
  • Same META-INF folder also has several spring-configuration files, most of them as "default", provided hibernate sessionFactory, transactionManager and other "default" beans. But it also has portlet-spring.xml which has definitions of our Service and related beans.

OK, nice, now, we may implement our UI used this generated service

Developing UI with using JSPPortlet

Again, based on simple JSF2.0/EJB3.1 PetCatalog sample we are going to display list of pets and allow user to see details of pet by clicking the link in the table row.

Define Portlet

First of all we need to define our portlet. Create target executed in the beginning already created dummy portlet for us. You can check next files:

  • docroot/WEB-INF/portlet.xml - contains definition of our petcatalog portlet. it said what portlet implemented in com.sample.jsp.portlet.JSPPortlet and /view.jsp is used to display portlet content. These settings is ok for us (at least for now), so, keep it like it is;
  • docroot/WEB-INF/liferay-portlet.xml - contains some additional (liferay specific) configuration for our portlet. Keep it like it is for now;
  • docroot/WEB-INF/src/com/sample/jsp/portlet/JSPPortlet.java - source of our portlet - currently it did not do anything - just said to display view.jsp;
  • docroot/view.jsp - this jsp will display some default text on portlet display;

So, by default SDK generated simple portlet - we will use it as start-point. So, lets test is it working: run liferay, and run deploy target in portlet ant. Checking liferay logs (if you are using Eclipse like I'm you will see them in Eclipse console view) we can see:
10:42:01,509 INFO  [PortletAutoDeployListener:87] Portlets for /home/akakunin/apps/liferay-portal-5.2.3/deploy/petcatalog-portlet-5.2.3.1.war copied successfully. Deployment will start in a few seconds.

Oct 15, 2009 10:42:08 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Loading file:/var/tmp/1-petcatalog-portlet/WEB-INF/classes/service.properties
10:42:08,644 INFO  [DialectDetector:64] Determining dialect for HSQL Database Engine 1
10:42:08,647 WARN  [DialectDetector:79] Liferay is configured to use Hypersonic as its database. Do NOT use Hypersonic in production. Hypersonic is an embedded database useful for development and demo'ing purposes. The database settings can be changed in portal.properties.
10:42:08,649 INFO  [DialectDetector:97] Using dialect org.hibernate.dialect.HSQLDialect
Loading file:/var/tmp/1-petcatalog-portlet/WEB-INF/classes/service.properties
Loading file:/var/tmp/1-petcatalog-portlet/WEB-INF/classes/service.properties
10:42:11,280 INFO  [ServiceComponentLocalServiceImpl:270] Running PetCatalog SQL scripts for the first time
10:42:11,880 INFO  [PortletHotDeployListener:227] Registering portlets for petcatalog-portlet
10:42:12,396 INFO  [PortletHotDeployListener:346] 1 portlet for petcatalog-portlet is available for use


Looks like portlet correctly deployed. Now lets check how it is working:

  • Open liferay UI in browser (http://localhost:8080),
  • Login as admin (if you have 7Coz portlet installed, you can simple click on "Login as Bruno Admin", if not, login by default admin: test@liferay.com/test)
  • Create new page ("test" for example) to place our portlet;
  • "Add Application" on this page - in popups with list of available applications/portlets we will able to find our "Pet Catalog" portlet in "Sample" category;
  • Great! We can see default text "This is the Sample JSP Portlet. Use this as a quick way to include JSPs" - everything works fine.


Fill data into DB

In our simplest sample we will not have UI to add new Pets, only to display, so, we need to place them there manually:

  • Stop Liferay;
  • Open any SQL Client you like (and depending from which DB your Liferay configured to use). By default Liferay configured to use HSQL. HSQL client (which also may be used as client-app) we can find in <path_to_liferay>/tomcat-6.0.18/lib/ext/hsql.jar and DB itself stored in <path_to_liferay>/data/hsql/lportal files.
  • So, open hsql client by run # java -cp <path_to_liferay>/tomcat-6.0.18/lib/ext/hsql.jar org.hsqldb.util.DatabaseManagerSwing
  • In opened configuration window use options to connect to file in file-system and use jdbc:hsqldb:file:<path_to_liferay>/hsql/lportal as URL
  • In opened window we will already see PETS_ITEM in the list of table - it is because Liferay already created it during first portlet deployment we only need to insert require data;
  • Execute sample.sql there (you can find this file in docroot/WEB-INF/sql/sample.sql in project archive);
  • Close client;

 

Display Table with pets

To display table with pets we need to change our view.jsp to by follow way:

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %> <%@ page import="javax.portlet.PortletURL" %> <%@ page import="javax.portlet.PortletPreferences" %> <%@ page import="javax.portlet.WindowState" %> <%@ page import="com.liferay.portal.kernel.dao.search.ResultRow" %> <%@ page import="com.liferay.portal.kernel.dao.search.SearchContainer" %> <%@ page import="com.liferay.portal.kernel.dao.search.SearchEntry" %> <%@ page import="petcatalog.model.Item" %> <%@page import="petcatalog.service.ItemLocalServiceUtil"%><portlet:defineObjects /> <%     PortletURL portletURL = renderResponse.createRenderURL();          // define list of headers we will use in the table     List<String> headerNames = new ArrayList<String>();     headerNames.add("name");     headerNames.add("photo");     headerNames.add("price");     // create search container, used to display table     SearchContainer searchContainer = new SearchContainer(renderRequest,                                                         null, null,                                                         SearchContainer.DEFAULT_CUR_PARAM,                                                         SearchContainer.DEFAULT_DELTA,                                                         portletURL,                                                         headerNames,                                                         "There No Pets To Display");     portletURL.setParameter(searchContainer.getCurParam(), String.valueOf(searchContainer.getCurValue()));          // get count of pets and list of pets to display on current page     int petsCount = ItemLocalServiceUtil.getItemsCount();     List<Item> pets = ItemLocalServiceUtil.getItems(searchContainer.getStart(),                                                     searchContainer.getEnd());          // set count into search container     searchContainer.setTotal(petsCount);          // fill table     List<ResultRow> resultRows = searchContainer.getResultRows();     for (int i=0; i < pets.size(); i++) {         Item pet = pets.get(i);         ResultRow row = new ResultRow(pet, pet.getItemId(), i);         row.addText(pet.getName(), "");         row.addJSP("/pet_image.jsp", application, request, response);         row.addText(String.valueOf(pet.getPrice()));         resultRows.add(row);     }              // and finally display it %> <liferay-ui:search-iterator searchContainer="<%= searchContainer %>" />

This code doing following:

  • Initialized SearchContainer - special Liferay-sepcific class used to display tables;
  • Get data (count of pets and specific subset of pets) from our ItemLocalServiceUtil service, generated by ServiceBuilder. As you can see - some basic functions were generated there automatically;
  • Fill SearchContainer with data to display;
  • Display table with using liferay-ui:search-iterator tag

As result, default Liferay table should be used to dispplay pets with many functionality like applying styles from theme, pagination and so on.
But - before we will able to see it we need to do some additional changes:

Display Pet Images

Do display pets images in the table we did:

row.addJSP("/pet_image.jsp", application, request, response);
So, we need to create required JSP file:

<%@ page import="com.liferay.portal.kernel.dao.search.ResultRow" %>
<%@ page import="com.liferay.portal.kernel.util.WebKeys" %>
<%@ page import="petcatalog.model.Item" %>

<%
ResultRow row = (ResultRow)request.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);

Item pet = (Item)row.getObject();
%>
<img src="<%= request.getContextPath() %>/images/<%=pet.getImageThumbUrl() %>"/>

Quite easy - it's get currently displayed Pet Item object and create image for it
Also, we need to copy images into docroot/images folder (you can get them in example sources)

Localization Support

String contstant, used in headerNames are resource-keys. To correctly display headers we need to add localization support. To do it - add resource-bundle tag into our portlet definition in portlet.xml like:
 

            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <resource-bundle>petcatalog.messages</resource-bundle>
        <portlet-info>
            <title>Projects</title>

And create docroot/WEB-INF/src/petcatalog/messages.properties file:
 

photo=Photo


name & price is already present in Liferay's resources, so, we do not need to translate them. Later you can easily add localization for your portlet
What is all - try to redeploy portlet and refresh the page



Display Pet Details

To display pet details we we need to change (improve) a little bit our portlet class.
So, lets change portlet.xml to point into new class:
 

        <portlet-name>petcatalog</portlet-name>
        <display-name>Pet Catalog</display-name>
        <portlet-class>petcatalog.portlet.PetCatalogPortlet</portlet-class>
        <init-param>

and create this class extending from  from com.liferay.util.bridges.jsp.JSPPortlet:
 

package petcatalog.portlet;
import com.liferay.util.bridges.jsp.JSPPortlet;
public class PetCatalogPortlet extends JSPPortlet {
}

Now we can remove old portlet package in com.sample and redeploy -everything should continue to work.
Then we need to change table to correctly generate link to pet details.
On view.jsp, in for loop change how data for first column was added:
 

        ResultRow row = new ResultRow(pet, pet.getItemId(), i);

        PortletURL petURL = renderResponse.createRenderURL();
        petURL.setWindowState(WindowState.MAXIMIZED);
        petURL.setParameter("pet_id", String.valueOf(pet.getItemId()));
        petURL.setParameter("redirect", com.liferay.portal.util.PortalUtil.getCurrentURL(request));
        row.addText(pet.getName(), petURL.toString());
        
        
        row.addJSP("/pet_image.jsp", application, request, response);

Here we created URL, added petId and currentURL as arguments and added it as link for first column.
Now, lets change our portlet to handle pet_id parameter:
 

public class PetCatalogPortlet extends JSPPortlet {
    @Override
    public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
        // check - do we have pet_id parameter
        Long petId = ParamUtil.getLong(renderRequest, "pet_id");
        if (petId != null && petId != 0l) {
            // try to get Pet
            try {
                Item pet = ItemLocalServiceUtil.getItem(petId);
                renderRequest.setAttribute("pet", pet);
            } catch (Exception ex) {
                throw new PortletException("Cannot get pet", ex);
            }
            include("/view_pet.jsp", renderRequest, renderResponse);
        } else {
            super.doView(renderRequest, renderResponse);
        }
    }
}

doView method called then portlet is rendered. We just check pet_id parameter, get Catalog Item by id, put it into request attributes and navigate into view_pet.jsp
Last thing - implement view_pet.jsp:
 

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>

<%@ page import="javax.portlet.PortletURL" %>
<%@ page import="javax.portlet.PortletPreferences" %>
<%@ page import="javax.portlet.WindowState" %>
<%@ page import="com.liferay.portal.kernel.util.UnicodeFormatter"%>

<%@ page import="petcatalog.model.Item" %>


<portlet:defineObjects />

<%
// get pet
Item pet = (Item)request.getAttribute("pet");
String redirect = request.getParameter("redirect");
%>

<table class="lfr-table">
    <tr>
        <td class="lfr-label"><liferay-ui:message key="name" />:</td>
        <td><%= pet.getName() %></td>
    </tr>
    <tr>
        <td class="lfr-label"><liferay-ui:message key="description" />:</td>
        <td><%= pet.getDescription() %></td>
    </tr>
    <tr>
        <td class="lfr-label"><liferay-ui:message key="photo" />:</td>
        <td><img src="<%= request.getContextPath() %>/images/<%=pet.getImageUrl() %>"/></td>
    </tr>
    <tr>
        <td class="lfr-label"><liferay-ui:message key="price" />:</td>
        <td><%= pet.getPrice() %></td>
    </tr>
</table>
<input id="cancelButton" type="button" value="<liferay-ui:message key='cancel' />" 
       onclick="location.href = '<%= UnicodeFormatter.toString(redirect) %>'"/>

This JSP will display Pet Details and image, Cancel button will navigate us back into list.


What's next?

Ok, we completed very simple portlet for Liferay. Even it is simple, it covered all parts of web-application - starting from UI and finishing DB, explained how to create new DB tables and Services in Liferay, how easily write UI sometimes with sometimes not so simple functionality (like paginator).
In next tutorials I hope to cover followed questions:

  • Add new Pet - process actions in portlets;
  • Permissions and Authorization for Items;
  • Better UI: use bookmarkable links;
  • Dependency management in Portlets;
  • and many many more.

 

0 Attachments 0 Attachments
27591 Views

Average (3 Votes)