Senior Software Architect for Liferay, Inc
Neil Griffin represents Liferay on the JSR 314 (JSF 2.0) expert group and has 15 years of professional experience in software engineering. As a Liferay project committer, Neil is responsible for interfacing with ICEsoft in order to ensure that ICEfaces integrates properly within Liferay Portal. Neil is the co-founder of the PortletFaces project which makes JSF portlet development easier. He has authored training classes for Liferay and ICEsoft, and has served as a consultant for clients implementing JSF and ICEfaces portlets.Presentations by Neil Griffin
Filthy Rich Portlets with ICEfaces and Liferay
When a portlet form is submitted, all the other portlets on the sameportal page are forced to redraw themselves. Learn how ICEfaces
Direct-to-DOM rendering provides a cure for this disruptive end-user
experience, and how ICEfaces Ajax Push supplies a rich alternative for
inter-portlet communication. Demonstrations will be performed within
Liferay Portal, a JSR 286 (Portlet 2.0) compliant portlet container.
"
Neil Griffin
<br />
<p>Iterator components like the <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/tlddocs/h/dataTable.html">JSF h:dataTable</a> and <a href="http://www.icesoft.com/developer_guides/icefaces/components/dataTableDoc.html">ICEfaces ice:dataTable</a> have two special attributes: <b>value</b> and <b>var</b>. The <b>value</b> attribute is typically bound to a JSF model-managed-bean, and the <b>var</b> attribute introduces a new variable into the JSF Expression Language (EL) like this:</p>
<p><code><ice:dataTable <b>value</b>="#{modelBean.rows}" <b>var</b>="row"><br />
<ice:column><br />
<f:facet name="header"><br />
<ice:outputText value="#{msgs.firstName}" /><br />
</f:facet><br />
<ice:outputText value=#{<b>row</b>.firstName}" /><br />
</ice:column><br />
...<br />
</ice:dataTable></code></p>
<p>The ModelBean.getRows() method can return a variety of types, but in my experience the most typical one is a generic java.util.List like this:</p>
<p><code>public List<Row> getRows() {...}</code></p>
<p>Under the hood, JSF is expecting the value attribute to contain an instance of a <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/javax/faces/model/DataModel.html">JSF DataModel</a> object. In fact, if you don't return a DataModel, JSF will implicitly wrap your return value with a wrapper class, such as the <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/javax/faces/model/ListDataModel.html">ListDataModel</a> for values of type java.util.List.</p>
<p>Typically, the model bean that contains the data is kept in JSF session scope or ICEfaces extended-request scope. This is fine if you only have 10 or 20 rows in the result set, but what if you have 1,000,000 rows? Obviously there is a scalability concern in such a case, especially since the user is only going to be viewing 10 or 20 rows at a time in the browser.</p>
<p>Unfortunately, the JSF spec does not provide a way of loading data on demand (popularly known as "lazy loading"). I've seen several posts on how to do this, but I thought it would be nice to develop a persistence-technology-independent solution.</p>
<p>To this end, I wrote an abstract class named LazyDataModel.java that is meant to be subclassed into a concrete implementation. Feel free to download the <a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1544410&name=DLFE-1016.zip">LazyDataModel.java</a> (zipped) source code.</p>
<p>Here are the three methods you will need to supply in your concrete implementation:</p>
<p><code>public abstract int getRowsPerPage();<br />
<br />
public abstract int countRows();<br />
<br />
public abstract List<DTO> findRows(int startRow, int finishRow);<br />
</code></p>
<p>The DTO marker implies that this class is meant to be used at the JSF UI layer of a portlet/webapp, and that it is meant to be used in conjunction with the <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html">Data Transfer Object</a> design pattern (formerly known as the Value Object / VO design pattern).</p>
<p>BTW, in order for this to be of benefit to the end user, you'll need to connect an <a href="http://www.icesoft.com/developer_guides/icefaces/tld/ice/dataPaginator.html">ICEfaces ice:dataPaginator</a> component to the ice:dataTable iterator. That will provide icons for navigation such as first-page, previous-page, next-page, and last-page.</p>
<p> </p>
<p>Iterator components like the <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/tlddocs/h/dataTable.html">JSF h:dataTable</a> and <a href="http://www.icesoft.com/developer_guides/icefaces/components/dataTableDoc.html">ICEfaces ice:dataTable</a> have two special attributes: <b>value</b> and <b>var</b>. The <b>value</b> attribute is typically bound to a JSF model-managed-bean, and the <b>var</b> attribute introduces a new variable into the JSF Expression Language (EL) like this:</p>
<p><code><ice:dataTable <b>value</b>="#{modelBean.rows}" <b>var</b>="row"><br />
<ice:column><br />
<f:facet name="header"><br />
<ice:outputText value="#{msgs.firstName}" /><br />
</f:facet><br />
<ice:outputText value=#{<b>row</b>.firstName}" /><br />
</ice:column><br />
...<br />
</ice:dataTable></code></p>
<p>The ModelBean.getRows() method can return a variety of types, but in my experience the most typical one is a generic java.util.List like this:</p>
<p><code>public List<Row> getRows() {...}</code></p>
<p>Under the hood, JSF is expecting the value attribute to contain an instance of a <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/javax/faces/model/DataModel.html">JSF DataModel</a> object. In fact, if you don't return a DataModel, JSF will implicitly wrap your return value with a wrapper class, such as the <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/javax/faces/model/ListDataModel.html">ListDataModel</a> for values of type java.util.List.</p>
<p>Typically, the model bean that contains the data is kept in JSF session scope or ICEfaces extended-request scope. This is fine if you only have 10 or 20 rows in the result set, but what if you have 1,000,000 rows? Obviously there is a scalability concern in such a case, especially since the user is only going to be viewing 10 or 20 rows at a time in the browser.</p>
<p>Unfortunately, the JSF spec does not provide a way of loading data on demand (popularly known as "lazy loading"). I've seen several posts on how to do this, but I thought it would be nice to develop a persistence-technology-independent solution.</p>
<p>To this end, I wrote an abstract class named LazyDataModel.java that is meant to be subclassed into a concrete implementation. Feel free to download the <a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1544410&name=DLFE-1016.zip">LazyDataModel.java</a> (zipped) source code.</p>
<p>Here are the three methods you will need to supply in your concrete implementation:</p>
<p><code>public abstract int getRowsPerPage();<br />
<br />
public abstract int countRows();<br />
<br />
public abstract List<DTO> findRows(int startRow, int finishRow);<br />
</code></p>
<p>The DTO marker implies that this class is meant to be used at the JSF UI layer of a portlet/webapp, and that it is meant to be used in conjunction with the <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html">Data Transfer Object</a> design pattern (formerly known as the Value Object / VO design pattern).</p>
<p>BTW, in order for this to be of benefit to the end user, you'll need to connect an <a href="http://www.icesoft.com/developer_guides/icefaces/tld/ice/dataPaginator.html">ICEfaces ice:dataPaginator</a> component to the ice:dataTable iterator. That will provide icons for navigation such as first-page, previous-page, next-page, and last-page.</p>
<p> </p>
<p><b>UPDATE!</b></p><p>An <b>archive</b> of the webinar is now available on ICEfaces .org in both .wmv and quicktime:<br /><a href="http://www.icefaces.org/main/resources/webinars.iface">http://www.icefaces.org/main/resources/webinars.iface</a> (3rd item in the Archive Webinar list)<br /><br />Also, here is a video+transcript of a similar presentation given at the <a href="http://www.jsfone.com">JSFOne</a> conference:<br /><a href="http://java.dzone.com/videos/filthy-rich-portlets ">http://java.dzone.com/videos/filthy-rich-portlets </a></p><p><b>Webinar Title:</b> Filthy Rich Portlets with ICEfaces and Liferay</p><p><b>Webinar Date/Time</b>: Tuesday, October 14, 2008 1:00 PM - 2:00 PM EDT</p><p><b>Webinar Link</b>: <a href="https://www1.gotomeeting.com/ojoin/451479046/1859554">https://www1.gotomeeting.com/ojoin/451479046/1859554</a></p> <p><b>Webinar Slides and Demo Downloads</b>:</p> <ul> <li><a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1362350&name=DLFE-943.pdf">Filthy Rich Portlet Slides</a> (PDF, 5.8MB)</li> <li><a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1362350&name=DLFE-947.zip">Source code for Demo #1: JSF Portlet</a> (Liferay Plugins SDK ZIP, 1.4 MB)</li><li><a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1362350&name=DLFE-945.zip">Source code for Demo #2: ICEfaces Portlet</a> (Liferay Plugins SDK ZIP, 4.7 MB)</li><li><a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1362350&name=DLFE-946.zip">Source code for Demo #3: JSF IPC Portlet</a> (Liferay Plugins SDK ZIP, 1.4 MB)</li><li><a href="http://www.liferay.com/c/document_library/get_file?p_l_id=745520&folderId=1362350&name=DLFE-944.zip">Source code for Demo #4: ICEfaces IPC Ajax Push Portlet</a> (Liferay Plugins SDK ZIP, 4.7 MB)</li></ul><p>The sources are meant to be extracted into the "plugins" folder of the Liferay Plugins SDK. Fore more info, download the the <a href="http://docs.liferay.com/4.3/official/liferay-43-plugins-guide.pdf">Liferay Plugins SDK documentation</a>.</p><p><b>Webinar Overview:</b></p><ul><li>Portals and Portlets</li><li>Liferay Portal</li><li>JSF Portlets</li><li>ICEfaces Portlets</li><li>Standard Inter-Portlet Communication</li><li>Ajax Push Inter-Portlet Communication</li></ul><p><a href="http://java.dzone.com/videos/filthy-rich-portlets "><br /></a> </p>
<p>Perhaps I'm dating myself, but when I entered college in 1988, the Computer Science department required me to buy a <a href="http://en.wikipedia.org/wiki/Macintosh_II">Mac II</a> because it was the only personal computer of the day that ran a type of Unix. No, not Mac OS X silly, but <a href="http://en.wikipedia.org/wiki/A/UX">A/UX</a>. I never really used the Mac OS back then -- always did my projects under A/UX. Recently my buddy <a href="http://weblogs.java.net/blog/edburns/">Ed Burns</a> invited me to a "classic arcade game" exhibit at the Orlando Science center, and there happened to be an exhibit of Apple computers that was the personal collection of someone on display. You guessed it, there was a Mac II on display! Yes, that's right -- the computer I used in college was in a museum. Hey, it had an 80MB hard drive, and all my engineering friends only had 20MB drives in their IBM PCs running MS-DOS.<br /><br />After I graduated, I sold my Mac II to another student. My first job had me using Windows 3.1, and I've been using Microsoft operating systems as a desktop OS ever since. I moved to Windows 95, NT4, Windows 2000, XP, and finally Vista.</p><p>I found that fresh installs of Windows XP were nice and fast. But after installing/uninstalling programs, Windows would catch a sickness that would make the OS slow down. I found that every 3-6 months I had to wipe my hard drive and reinstall XP in order to get it back to top speed. My experience with Vista was much the same. After it got slow a second time, I decided I needed a change.</p><p>I almost went with Ubuntu Linux, but I had a non-Liferay email account that required Microsoft Exchange connectivity. So I ended up going with the Mac.</p><p>Yes, after 20 years (almost to the very day), I'm driving a Mac again. How about that!</p><p> </p>
<p><b>UPDATE!</b></p> <p>Here is a <b>video+transcript</b> of the presentation given at the <a href="http://www.jsfone.com">JSFOne</a> conference:<br /> <a href="http://java.dzone.com/videos/filthy-rich-portlets ">http://java.dzone.com/videos/filthy-rich-portlets </a></p> <p>And here is a similar <b>webinar</b> on ICEfaces .org in both .wmv and quicktime:<br /> <a href="http://www.icefaces.org/main/resources/webinars.iface">http://www.icefaces.org/main/resources/webinars.iface</a> (3rd item in the Archive Webinar list)<br /> </p> <hr /><p>The <a href="http://www.jsfone.com/conference/washington_dc/2008/09/index.html">JSFOne</a> conference is being held from Thu 9/4/2008 - Sat 9/6/2008 in the Washington, DC area. Specifically, the event is being held at the Sheraton Premier Hotel in Vienna, VA.</p><p> </p> <p>I have the great privilege of speaking at the conference, and will be doing a talk titled "<a href="http://www.jsfone.com/speaker_topic_view.jsp?topicId=1461">Filthy Rich Portlets with ICEfaces and Liferay</a>." To that end, I recently committed a new portlet to the Liferay Plugins trunk: <a href="http://lportal.svn.sourceforge.net/viewvc/lportal/plugins/trunk/portlets/sample-icefaces-ipc-ajax-push-portlet/">sample-icefaces-ipc-ajax-push-portlet</a>.</p> <p>It's actually one web archive (WAR) that contains two portlets. The <b>Customers</b> <b>portlet</b> simply shows a list of customers:</p> <p><img width="358" height="147" src="http://www.liferay.com/image/image_gallery?img_id=1205814&t=1219279796647" alt="" /></p> <p>When the user clicks on a customer, ICEfaces initiates an Ajax-Push event which updates the <b>Bookings</b> <b>portlet</b> with travel plans of the selected customer:</p> <p><img width="431" height="361" src="http://www.liferay.com/image/image_gallery?img_id=1205810&t=1219279789513" alt="" /></p> <p>But wait! There's more -- the IPC is bi-directional in the sense that if I change the first name or last name in the Bookings portlet, then the name will be updated in the Customers portlet too, all thanks to ICEfaces Ajax Push.</p> <p>See you at JSFOne!</p> <p> </p>
<p>If you want to share data between portlets, a common way to do that is by storing data in the user's session. Liferay Portal has some nice features for sharing session data like <shared-session-attributes> in the liferay-portlet.xml file, and shared.session.attributes in the portal-ext.properties file.</p> <p>But what if you're trying to go <b>session-less</b>? Or what if you want to share data <b>globally</b> for all users across sessions?</p> <p>One way to do share data like this is to store the data in the portal's ROOT context. The first step is to create a GlobalStartupAction in the Liferay EXT environment, like this:</p> <pre>
package com.foo.liferay.startup;
public class GlobalStartupAction extends SimpleAction {
private static List<String><string> fooList; <br /> public List<String> getFooList() {<br /> return fooList;<br /> }<br /><br /><string> public static void <br /> @Override public void run(String[] ids) throws ActionException {<br /> // Cache all the data<br /> fooList = new ArrayList<String><string>();<br /> fooList.add(new String("red"));<br /> fooList.add(new String("green"));<br /> fooList.add(new String("blue"));<br /> }<br />}<br /><br /></string></string></string></pre> <p>Then, the GlobalStartupAction needs to be registered in the portal-ext.properties file. There is already a global startup action specified there for Liferay's own startup purposes, but thankfully this value can be a comma-delimited list, so we can just add the GlobalStartupAction after the Liferay one:</p> <p>global.startup.events=com.liferay.portal.events.GlobalStartupAction<b>,com.foo.liferay.startup.GlobalStartupAction</b></p> <p>And then inside each portlet, use the magic Liferay PortalClassInvoker to call the static getFooList() method. The PortalClassInvoker utility figures out the classloader for the Liferay Portal context before calling the specified method:</p> <pre>
import com.liferay.portal.kernel.util.PortalClassInvoker;
public class ApplicationScopeBean {
private static final List<string> fooList;<br /><br /> public ApplicationScopeBean() {<br /> List<String> fooList = (List<String>) PortalClassInvoker.invoke("com.foo.liferay.startup.GlobalStartupAction", "getFooList"));</string></pre><pre><string> }<br /><br />}<br /></string></pre><p> </p>
package com.foo.liferay.startup;
public class GlobalStartupAction extends SimpleAction {
private static List<String><string> fooList; <br /> public List<String> getFooList() {<br /> return fooList;<br /> }<br /><br /><string> public static void <br /> @Override public void run(String[] ids) throws ActionException {<br /> // Cache all the data<br /> fooList = new ArrayList<String><string>();<br /> fooList.add(new String("red"));<br /> fooList.add(new String("green"));<br /> fooList.add(new String("blue"));<br /> }<br />}<br /><br /></string></string></string></pre> <p>Then, the GlobalStartupAction needs to be registered in the portal-ext.properties file. There is already a global startup action specified there for Liferay's own startup purposes, but thankfully this value can be a comma-delimited list, so we can just add the GlobalStartupAction after the Liferay one:</p> <p>global.startup.events=com.liferay.portal.events.GlobalStartupAction<b>,com.foo.liferay.startup.GlobalStartupAction</b></p> <p>And then inside each portlet, use the magic Liferay PortalClassInvoker to call the static getFooList() method. The PortalClassInvoker utility figures out the classloader for the Liferay Portal context before calling the specified method:</p> <pre>
import com.liferay.portal.kernel.util.PortalClassInvoker;
public class ApplicationScopeBean {
private static final List<string> fooList;<br /><br /> public ApplicationScopeBean() {<br /> List<String> fooList = (List<String>) PortalClassInvoker.invoke("com.foo.liferay.startup.GlobalStartupAction", "getFooList"));</string></pre><pre><string> }<br /><br />}<br /></string></pre><p> </p>