If a picture is worth a thousand words, a live demo is worth a million. The problem is that a live demo takes as much work as the real thing and is frequently a throw away. Quality demos also can often require almost as much hardware and software as the final product, making it difficult to get enough funding to build something to get funding. Most difficult of all, it is seldom the person who wrote the demo who will present it, so it must be both simple to use and portable.
In the case of building a Proof Of Concept (POC) for a portal, you can solve all of the above by using the Open Source Apache Struts running on Jakarta Tomcat. With only a few lines of code and minor modifications, you can build a demo portal that can be run on a laptop by anyone who knows how to click an icon.
The key to the POC portal is in leveraging Struts Tiles. Portals are, in essence, a set of consistent layouts with different components. Struts Tiles are a continuation (seems like a replacement, now) of Struts Templates. Tiles leverage the JSP include feature to facilitate the reuse of presentation components in a consistent manner. Having built many portals as a consultant, the first time I saw a Tiles example (in someone else’s online article for which I have lost the URL), I thought, “That is a portal.”
Tiles vs. Templates
One major improvement Tiles has over Templates is the use of runtime values1. This feature makes it easy to create portal page layouts with Tiles that are reusable. Tiles also make it easy for sharing values between components (if you’ve ever hand-coded JSPs using includes that need to share values, you can appreciate anything that makes this easier). It’s this combination of features that you can leverage to create a flexible and reusable portal POC framework.
Anatomy of a Portal
A portal framework consists of a header, footer, navigation, page, and portlets. Pages have layouts, and portlets are made up of a container, a title bar, and a body. The portlet body is where your individual components live, so everything else is reusable.
In this article, you’ll look at the configurations and components necessary to create a very simple portal. Once you understand the basics, you can build as complex of an application as you like.
Building the Portal
For each element of our framework, you need at least one JSP. Because this is not an article on UI development, the JSPs will consist only of a line of text that identifies the file name and the related CSS value so that, when the application is running, these components will be obvious in the browser (also a convenient way to hand off your demo app to a UI developer to add a pretty face). Each framework JSP needs to have access to the Tiles API and any necessary messages. A fairly simple example is your header:
<%-- header.jsp --%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <%@ page import="java.util.Calendar, java.text.DateFormat"%> <% Calendar c = Calendar.getInstance(); DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); df.setCalendar(c); %> File: header.jsp Class: portal-body-header <span style="position:absolute; top:35px; right:10px"> <%= df.format(c.getTime()) %> </span>
The date functionality is included here to make it immediately obvious to the casual audience that your demo is more than just static HTML.
Working from the opposite end, most portals have a footer. Because demo applications frequently do not have enough data to fill a 1024X768 screen, you’ll use a little scripting2 to make sure the footer stays at the bottom of the page, like this:
<%-- footer.jsp --%> <div id="pageFooter" align="center" style="position: absolute; bottom: 0px; visibility:hidden;"> File: footer.jsp Class: portal-body-footer </div> <script language="JavaScript"> var pageHeight = document.body.scrollHeight; var pageFooter = document.getElementById("pageFooter"); var footerLocation = document.getElementById("pageFooter"); var topX = footerLocation.offsetParent.clientTop; while (footerLocation) { topX += footerLocation.offsetTop; footerLocation = footerLocation.offsetParent; } if(topX+20 < pageHeight) { pageFooter.style.top = pageHeight+10+"px"; } pageFooter.style.visibility = "visible" </script>
The remainder of your JSPs will reference information from the Tiles configuration file (tiles-defs.xml), so you should build that next.
Inside the <tiles-definitions> tags in tiles-defs.xml, you’ll configure your framework, starting with your first layout:
<!-- LAYOUTS --> <definition name=".mainLayout" path="/framework/body.jsp"> <put name="title" value="Portal POC" /> <put name="header" value="/framework/header.jsp" /> <put name="footer" value="/framework/footer.jsp" /> <put name="container" value=".container" /> <put name="page" value=".page" /> <put name="menu" value=".mainMenu" /> <put name="titleBar" value=".titleBar" /> <!-- These will create navigation --> <putList name="navLabels"> <add value="Home" /> <add value="Directory" /> <add value="Resources" /> </putList> <putList name="navLinks"> <!-- SEE struts-config.xml --> <add value="home.do" /> <add value="directory.do" /> <add value="resources.do" /> </putList> </definition>
With the above configuration, you are setting your mainLayout page as /framework/body.jsp, which is where you will begin the assembly of your portal components. The other entries set values that can be used in your portal for layout and presentation. The title element for, example, is simply a string used as a consistent way to access values.
Note: Although not necessary, it is good to make it a habit to avoid hard coding values whenever possible so that you will be able reuse more of your POC work in a production application or to give to junior team members as a template for development.
The putList values will be used for building your navigation. How this works will become clearer as you build the other pieces required for a portal-style navigation.
Values that start with a “.” are defined separately, as shown below:
<!-- Framework Items --> <definition name=".page" path="/framework/page.jsp" /> <definition name=".container" path="/framework/container.jsp" /> <definition name=".titleBar" path="/framework/titlebar.jsp" /> <definition name=".mainMenu" path="/framework/horizontalmenu.jsp" />
Although the simple examples shown here may not fully utilize this design all of the framework items above, they are included to convey the flexibility your “lite” application can provide (one such application is currently in production at a global corporation). Also, most of the demo code in this article is re-used from an application I use to introduce developers to portal concepts.
Creating Portal Page Definitions
Finally, you create portal page definitions that implement the framework definitions. Again, this is nothing fancy because this is strictly to show how the approach works. Each page definition extends your mainLayout, adding elements specific to the page such as the currentTab value (which will be used in the navigation JSP to provide the You Are Here mode), the pageBody value that points to a JSP with a layout for our portlets, and list of portlet tiles (the actual content) and titles (so your titlebar has something to work with).
To make this all work, you need to have some corresponding entries in the <action-mappings> node of struts-config.xml3:
<action path="/home" type="org.apache.struts.actions.ForwardAction" parameter="homePage" /> <action path="/directory" type="org.apache.struts.actions.ForwardAction" parameter="directory" /> <action path="/resources" type="org.apache.struts.actions.ForwardAction" parameter="resources" />
Just to get things started, you’ll create some empty JSPs for each listed in your tiles-definitions, and then fire up Tomcat and look at what you have so far:
Next, you’ll add the minor coding necessary to make your Tiles site act like a portal application (for convenience and to encourage reuse, the associated JSPs live in a framework path).
<%-- body.jsp --%> <%@ page import="org.apache.log4j.Logger" errorPage="error.jsp"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <% Logger logger = Logger.getLogger(this.getClass().getPackage().getName());%> <html> <head> <title><tiles:getAsString name="title"/></title> <link rel="STYLESHEET" type="text/css" href="framework/demo.css"> </head> <body class="portal-body"> <%-- SEE WEB-INFtiles-defs.xml --%> <tiles:useAttribute name="currentTab" classname="java.lang.String" /> <tiles:useAttribute name="showMenu" classname="java.lang.String" /> <tiles:useAttribute name="menu" classname="java.lang.String" /> <tiles:useAttribute name="pageBody" classname="java.lang.String" /> <tiles:useAttribute name="titleBar" classname="java.lang.String" /> <tiles:useAttribute name="container" classname="java.lang.String" ignore="true" /> <tiles:useAttribute name="tiles" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="titles" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="navLabels" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="navLinks" classname="java.util.List" ignore="true" /> <div id="pageBody" class="portal-body-content"> Page: body.jsp class: portal-body <tiles:insert attribute="header"/> <tiles:insert attribute="page"> <tiles:put name="currentTab" value="<%= currentTab %>" /> <tiles:put name="pageBody" value="<%= pageBody %>" /> <tiles:put name="titles" value="<%= titles %>" /> <tiles:put name="showMenu" value="<%= showMenu %>" /> <tiles:put name="menu" value="<%= menu %>" /> <tiles:put name="navLabels" value="<%= navLabels %>" /> <tiles:put name="navLinks" value="<%= navLinks %>" /> <tiles:put name="tiles" value="<%= tiles %>" /> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="container" value="<%= container %>" /> </tiles:insert> </div> <tiles:insert attribute="footer"/> </body> </html>
The useAttribute tags let you create a request variable that can be passed from component to component, such as currentTab, which is used by the navigation JSP for sense of place.
In the case of variables that may or may not exist in a given context, you have the ignore parameter (default is false). In body.jsp, you are using three components directly with the insert tag:
<tiles:insert attribute="header"/> <tiles:insert attribute="page">... <tiles:insert attribute="footer"/>
The page component will need values from your body, so you insert them like this:
<tiles:put name="currentTab" value="<%= currentTab %>" /> <tiles:put name="pageBody" value="<%= pageBody %>" /> <tiles:put name="titles" value="<%= titles %>" /> <tiles:put name="showMenu" value="<%= showMenu %>" /> <tiles:put name="menu" value="<%= menu %>" /> <tiles:put name="navLabels" value="<%= navLabels %>" /> <tiles:put name="navLinks" value="<%= navLinks %>" /> <tiles:put name="tiles" value="<%= tiles %>" /> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="container" value="<%= container %>" />
Following this trail, you next go to your page.jsp:
<%-- page.jsp --%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:useAttribute name="currentTab" classname="java.lang.String" /> <tiles:useAttribute name="pageBody" classname="java.lang.String" /> <tiles:useAttribute name="showMenu" classname="java.lang.String" /> <tiles:useAttribute name="navLabels" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="navLinks" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="tiles" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="titleBar" classname="java.lang.String" ignore="true" /> <tiles:useAttribute name="container" classname="java.lang.String" ignore="true" /> <% if(showMenu.equalsIgnoreCase("true")){%> <divclass="horizontal-menu-single-container" style="width:100%"> <tiles:insert attribute="menu"> <tiles:put name="currentTab" value="<%= currentTab %>" /> <tiles:put name="navLabels" value="<%= navLabels %>" /> <tiles:put name="navLinks" value="<%= navLinks %>" /> </tiles:insert> </div> <%}%> <divclass="portal-primary-page"> File: page.jsp class: portal-primary-page <tiles:insert attribute="pageBody"> <%-- SEE tiles-defs.xml --%> <tiles:put name="tiles" value="<%= tiles %>" /> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="container" value="<%= container %>" /> <tiles:put name="navLabels" value="<%= navLabels %>" /> <tiles:put name="navLinks" value="<%= navLinks %>" /> <tiles:put name="currentTab" value="<%= currentTab %>" /> </tiles:insert> </div>
In page.jsp, the useAttribute tags can only access what has been passed in from body.jsp. page.jsp has the menu component and the pageBody component, which it passes on the values needed by each.
horizontalmenu.jsp is the first component that takes advantage of your list items.
<!-- horizontalmenu.jsp --> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:useAttribute name="navLabels" classname="java.util.List" /> <tiles:useAttribute name="navLinks" classname="java.util.List" /> <tiles:useAttribute name="currentTab" classname="java.lang.String" /> <%-- HORIZONTAL MENU --%> <divclass="portal-primary-menu-container"> File: horizontalmenu.jsp class: portal-primary-menu-container <table border="1" cellpadding="0" cellspacing="0" id="menu"> <tr> <% for(int i=0; i < navLabels.size(); i++){%> <td nowrap <%if(navLabels.get(i).equals(currentTab)){%> style="font-weight : bold; background-color : #ECECEC" <%}else{%> style="cursor:pointer;" onclick="location.href='<%= navLinks.get(i) %>';" <%}%> > <%= navLabels.get(i) %> </td> <% }%> </table> </div>
Here, you loop through the navLabels and navLinks Lists to create your menu, and check your currentTab value to create a sense of place style.
Page Layout
Back in page.jsp, your pageBody value tells you which page layout to use. You’ll look at the simple home layout here. Other layouts are available in the example download.
<%-- home.jsp --%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:useAttribute name="tiles" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="titles" classname="java.util.List" ignore="true" /> <tiles:useAttribute name="container" classname="java.lang.String" /> <tiles:useAttribute name="titleBar" classname="java.lang.String" ignore="true" /> <table cellpadding="0" cellspacing="0" border="0" width="100%"> <tr><td colspan="2">File: home.jsp</td></tr> <tr> <td valign="top" width="50%"> <tiles:insert attribute="container"> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="tile" value="<%= (String)tiles.get(0) %>" /> </tiles:insert> <tiles:insert attribute="container"> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="tile" value="<%= (String)tiles.get(1) %>" /> </tiles:insert> </td> <td valign="top" width="50%"> <tiles:insert attribute="container"> <tiles:put name="titleBar" value="<%= titleBar %>" /> <tiles:put name="tile" value="<%= (String)tiles.get(2) %>" /> </tiles:insert> </td> </tr> </table>
This page template requires three tiles. With a little more effort, you can create more dynamic templates like (again, see the download). Note that you do as little formatting as possible here, to mirror most portal products. You simply set the location of the portlets (aka Tiles) and let the container component handle the rest.
Of course, the container component is just there to manage the portlet, so like all good managers it does nothing more than delegate the work, like this:
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:useAttribute name="title" classname="java.lang.String" ignore="true" /> <tiles:useAttribute name="tile" classname="java.lang.String" ignore="true" /> <tiles:useAttribute name="titleBar" classname="java.lang.String" /> <divclass="portal-container"> <tiles:insert attribute="titleBar"> File: container.jsp Class: portal-container <tiles:put name="title" value="<%= title %>" /> </tiles:insert> <tiles:insert page="<%= tile %>" flush="true"/> </div>
titleBar doesn’t do much more. It simply puts the title where you would expect it, and provides some UI for it:
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:useAttribute name="title" classname="java.lang.String" /> <tiles:useAttribute name="link" classname="java.lang.String" ignore="true" /> <divclass="portal-titlebar"> File: titlebar.jsp Class: portal-titlebar Title:<%= title %> </div>
And, finally, the tile value points to your portlet content, which can be a JSP or HTML with your portlet content. Here’s what a bare example looks like:
Once the framework is defined in tiles-defs.xml and you have built your framework JSPs, you can create everything that a portal consists of without writing another line of code. Just drop your demo portlet HTML into the application, update your config file, and you’ve updated the portal.
Obviously, it won’t make a good demo with file name strings all over, so be sure to remove them once you have built an attractive UI, like this:
Giving non-tech users a way to play with your work while you’re not there is a reality of development. By embedding Tomcat and Java into a Web application, anyone (even your boss) can start the application with two double-clicks.
Imbedding Tomcat and Java
To imbed Tomcat and Java, create a folder to hold your full demo as shown below:
I call mine CD, because the idea is to burn it to a CD for copying to laptops. In CD, you will need Tomcat, a start batch file, a stop batch file, and a link to your application.
start.bat is simply a modification of the one Tomcat provides and looks like this:
@echo off if "%OS%" == "Windows_NT" setlocal rem ------------------------------------------------------------- rem Start script for the CATALINA Server rem rem $Id: startup.bat,v 1.4 2002/08/04 18:19:43 patrickl Exp $ rem ------------------------------------------------------------- rem Guess CATALINA_HOME if not defined if not "%CATALINA_HOME%" == "" goto gotHome set CATALINA_HOME=. set CATALINA_HOME="%CATALINA_HOME%jakarta-tomcat-5.0.19" deltree "%CATALINA_HOME%workCatalina" if exist "%CATALINA_HOME%bincatalina.bat" goto okHome set CATALINA_HOME=.. :gotHome if exist "%CATALINA_HOME%bincatalina.bat" goto okHome echo The CATALINA_HOME environment variable is not defined correctly echo This environment variable is needed to run this program goto end :okHome set JAVA_HOME=%CATALINA_HOME%j2sdk1.4.1_03 set EXECUTABLE=%CATALINA_HOME%bincatalina.bat rem Check that target executable exists if exist "%EXECUTABLE%" goto okExec echo Cannot find %EXECUTABLE% echo This file is needed to run this program goto end :okExec rem Get remaining unshifted command line arguments and save them rem in the set CMD_LINE_ARGS= :setArgs if ""%1""=="""" goto doneSetArgs set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 shift goto setArgs :doneSetArgs call "%EXECUTABLE%" start %CMD_LINE_ARGS% :end Likewise with stop.bat: @echo off if "%OS%" == "Windows_NT" setlocal rem ------------------------------------------------------------- rem Stop script for the CATALINA Server rem rem $Id: shutdown.bat,v 1.3 2002/08/04 18:19:43 patrickl Exp $ rem ------------------------------------------------------------- rem Guess CATALINA_HOME if not defined if not "%CATALINA_HOME%" == "" goto gotHome set CATALINA_HOME=. set CATALINA_HOME="%CATALINA_HOME%jakarta-tomcat-5.0.19" if exist "%CATALINA_HOME%bincatalina.bat" goto okHome set CATALINA_HOME=.. :gotHome if exist "%CATALINA_HOME%bincatalina.bat" goto okHome echo The CATALINA_HOME environment variable is not defined correctly echo This environment variable is needed to run this program goto end :okHome set JAVA_HOME=%CATALINA_HOME%j2sdk1.4.1_03 set EXECUTABLE=%CATALINA_HOME%bincatalina.bat rem Check that target executable exists if exist "%EXECUTABLE%" goto okExec echo Cannot find %EXECUTABLE% echo This file is needed to run this program goto end :okExec rem Get remaining unshifted command line arguments and save them rem in the set CMD_LINE_ARGS= :setArgs if ""%1""=="""" goto doneSetArgs set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 shift goto setArgs :doneSetArgs call "%EXECUTABLE%" stop %CMD_LINE_ARGS% :end
Your application link points like this:
http://localhost/mydemo/
Place the JDK inside your Tomcat path (j2sdk1.4.1_03 in this example).
User-Friendly Updates
To make updates simple, you want to deploy this as a war file, and you don’t want the non-technical users to have to do anything other than replace the war file in webapps, so set server.xml to not explode wars like this:
<Host name="localhost" debug="0" appBase="webapps" unpackWARs="false" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
This initial demo helped in the sale of three BI initiatives within six months. The demo was so easy to use by the presentation team that I didn’t know it had been part of the presentations until after the projects were completed.
One complaint about the initial demo was that the data was stale. I solved this problem by using POI HSSF and making minimal changes to the project-provided examples. HSSF is a real handy project for reading and writing to Microsoft Excel file, but it is not built for speed. You can download my POC portal application and see how simple it was to plug in HSSF as a data source. Essentially, it is called by a Struts action class using a Form Bean (not included) that stores the last update timestamp of the spreadsheet along with the data values in application scope. You can use the timestamp to determine whether to repopulate your bean with new data to improve performance.
When I first began learning Java, one of the developers I worked with would often say “Reuse is everything.” Time and experience have taught me that writing every component to be reusable defeats the purpose of doing so (decreasing effort). The key to writing reusable code that returns its promise is in identifying a future use before making it reusable. Open Source implementations and portals are the perfect opportunity to write reusable components such as the light-weight portal described in this article.
Downloads
- Tomcat 5.5.x (http://archive.apache.org/dist/jakarta/tomcat-5/)
- Tomcat Compatibility (same path as tomcat)
- Struts (http://struts.apache.org/download.cgi)
- POI (http://jakarta.apache.org/poi/index.html)
- JDK (http://java.sun.com/j2se)
- Example Portal (Download.zip)
Footnotes
1 One reason I like Open Source solutions is that if something is almost what you want, it’s possible to change it to what you do want. A little tweaking of the Templates TLD and some minor coding allowed me to adapt Templates to take run-time values and saved days of work on a major Struts-based project.
2 The script was build hastily and only works properly in Internet Explorer. It’s not difficult to tweak it to be cross-browser compatible; I haven’t done that because I have had the luxury of building Intranet portals for some time now that will only be used on IE.
4 Open Source components go through rapid version changes. The versions this article is based on my have changed enough where you may need to reference current documentation to implement everything. Packaging also changes, so you may need to download support libs for these projects.
About the Author
Scott Nelson is a Principal Technical Consultant at for a professional services company based in Cambridge, MA.. His client engagements over the last three years have focused on the delivery of business intelligence to leadership teams in large multi-national companies through portal applications. Between client projects, he leads and develops prototype implementations of new technologies and products.