Build portals with Jetspeed

Use Apache Jetspeed to build portals out of Web services

Developers are facing a new application integration challenge: building existing content, server-side Java applications, and Simple Object Access Protocol (SOAP)-based Web services into a coherent frontend application for their users. Most of you are familiar with consumer-oriented Websites -- such as Lycos, AltaVista, and Yahoo! -- that aggregate content and services into portals. Company intranets have used portals for years to make internal systems available to employees. Those intranets tend to be either expensive proprietary solutions or internally supported homegrown portals. If you're looking for an open source way to develop your own portal solution, look no further than the Jetspeed project from the Apache Software Foundation. For Java developers, Jetspeed offers a Portal API for developing small Java applications, known as portlets, that run inside the portal. The IBM WebSphere Portal Server also supports that Portlet API.

What does Jetspeed do?

Jetspeed lets you focus on building connections to outside resources, such as Web services, databases, and content feeds. It features built-in services for user interface customization, caching, persistence, and user authentication. As a portal developer, you don't have to build any of those services yourself; instead, you can concentrate on retrieving external data and displaying it. Jetspeed doesn't place any restrictions on what resources portlets may access.

Each user has individual settings for displaying portlets on his or her portal, for both wireless and Web access. Some portlets may only work on the Web, while some may also work on mobile devices; users can have different portlets for each. User authentication is abstracted through interfaces, and you can implement the authenticate() method on the UserManager interface from Turbine (provided as part of Jetspeed) or you can replace the method with a pluggable authentication module. You could use that module as part of a single sign-on solution, in which your portal handles frontend authentication, or to access an existing database of user information.

To display content, portlets use the Element Construction Set (ECS) API, which generates markup elements from Java objects. ECS supports the Wireless Markup Language (WML) as well as HTML and XML, and is open source under the Apache license. It is available from the Jakarta Apache Project; however, the ECS jar file is bundled with Jetspeed, so no additional downloads are necessary. It may be easier to use a servlet-based template or Web publishing technology, such as JSPs, WebMacro, or Velocity, to generate content for your portlet. ECS can run a servlet and capture the output in an ECS element, which may then be used as the displayed content.

Without any Java programming, you can easily set up Jetspeed to get news headlines and content from other Websites. Jetspeed can use both the RSS (RDF Site Summary) and OCS (Open Content Syndication) formats. RSS is an XML format used for syndicating Web headlines. Websites publish RSS feeds to anyone on the Internet interested in retrieving them. The headlines link back to the publishing Website for the article's full content. The OCS format describes multiple-content channels, including RSS headlines. To configure new content channels for Jetspeed, add them to your WEB-INF/conf/jetspeed-config.jcfg file.

Jetspeed architecture

Jetspeed is built on top of Turbine, an open source application framework from the Jakarta Apache Project. Most Apache projects are either built on Turbine or integrated with it. Some of Jetspeed's concepts, such as screens and user information, are borrowed from Turbine.

Jetspeed can sit on a number of servlet runners and databases. We'll be using the Tomcat 3.2 servlet runner, also from the Jakarta Apache Project. Tomcat also acts as a Web server, so you won't need an additional HTTP server.

Jetspeed is bundled with Thomas Mueller's Hypersonic SQL database. Tables are already created and populated with user data in Hypersonic SQL. Hypersonic SQL runs in process to Jetspeed (and Tomcat), so no additional configuration is necessary. If you want to use a different database, such as Oracle, DB2, Sybase, MySQL, or Postgres, you must set up the database using the SQL scripts included with the Jetspeed source code. In addition, you must configure the TurbineResources.properties file that Jetspeed and Turbine use to point to the new database server.

I recommend that you use the Hypersonic SQL database. The most common complaint on the Jetspeed users mailing list is that configuring a new database, like MySQL, doesn't work. If you do run into problems with another database, first check the Jetspeed and Turbine mailing list archives for other developers' experiences with the software (see Resources). Jetspeed uses Turbine's database connection code. Of course, you can use any database from your portlet; Jetspeed just uses Hypersonic SQL internally.

Install Jetspeed

To install Jetspeed, follow these three steps:

  • Download and install Tomcat 3.2.
  • Download the Jetspeed binary distribution (currently 1.3a1) and place the war file in Tomcat's webapps directory.
  • Download Apache James if you don't already have an SMTP server running on your machine. Configure the mail.server property in Jetspeed's TurbineResources.properties, which resides in the webapps/jetspeed/WEB-INF/conf subdirectory under the directory where you installed Tomcat.

Detailed installation instructions are available on the Jetspeed Webpage; see Resources for a link.

Jetspeed is packaged as a war file, and can be dropped into Tomcat's webapps directory. You'll need to restart Tomcat if it's already running.

The testing URL for Jetspeed is http://localhost:8080/jetspeed/. If you change Tomcat's default port, replace 8080 with your new port number. You can log in using turbine as both your username and password, or just use the default configuration without logging in.

Develop new portlets

When you develop new portlets for Jetspeed, use the AbstractPortlet class as a base. This is in the org.apache.jetspeed.portal.portlets package. By extending AbstractPortlet, you can have a working portlet by implementing the getContent() method. The getContent() method also takes an implementation of the RunData interface from Turbine as a parameter. By accessing get or set methods from the RunData interface, you have access to the runtime information stored in Turbine, including cookies, locale, and user data. Most of this data is actually handled by Jetspeed itself, and isn't needed for portlet development.

The getContent() method returns a ConcreteElement from the ECS. The ECS objects that represent HTML or WML tags extend either MultiPartElement or SinglePartElement, both of which extend ConcreteElement. You can use plain old strings in the StringElement without markup. The StringElement also subclasses ConcreteElement.

Our first portlet is fairly simple and demonstrates the use of ECS to build an HTML table. Our usage of ECS is fairly straightforward: elements may have set attributes, or you may include additional elements; tags, such as <table>, <tr>, and <td>, are nested. ECS can look strange at first, but the advantage of using it is the flexibility it provides in moving content around. Your tags will always be properly nested when you cut and paste your HTML markup.

When you compile the portlet, be sure that ecs.jar, turbine.jar, and jetspeed-1.3a1.jar are in your classpath. Move the class file into webapps/jetspeed/WEB-INF/classes under your Tomcat home directory.

import org.apache.ecs.*;
import org.apache.ecs.html.*;
import org.apache.jetspeed.portal.portlets.AbstractPortlet;
import org.apache.turbine.util.RunData;
import java.io.*;
public class FirstPortlet extends AbstractPortlet {
   public ConcreteElement getContent (RunData aRunData) {
      Table table = new Table()
                       .setBorder(0)
                               .addElement(new TR()
                                  .addElement(new TD()
                                     .addElement(new B()
                                       .addElement("I am bold."))))
                               .addElement(new TR()
                                  .addElement(new TD()
                                     .addElement("I'm the lower table row.")));
      return table;
   }
}

Here is the HTML this portlet displays:

<table border="0">
    <tr>
        <td>
            <b>
                I am bold.
            </b>
        </td>
    </tr>
    <tr>
        <td>
            I'm the lower table row.
        </td>
    </tr>
</table>

Portal Structure Markup Language

You must register your new portlet with Jetspeed manually, using an XML dialect called Portal Structure Markup Language (PSML). Jetspeed uses PSML to describe internal configuration, available portlets, and user configurations. The available portlets are stored in the portlet registry. Each portlet is described in PSML as a portlet-entry element, which tells Jetspeed how to instantiate the portlet. Each portlet entry must have an inheritance type associated with it:

  • instance: This is a standalone portlet. The instance entry holds all information needed to run the portlet.
  • abstract: This portlet entry does not have all the information needed, so a ref type portlet must provide additional entry data. The RSS portlet included with Jetspeed is an abstract portlet. To be used, you must create another entry of type ref with a URL pointing to the RSS content.
  • ref: A ref portlet entry builds upon another portlet entry; you can build a chain of ref portlet entries. The Mozilla portlet bundled with Jetspeed is a ref entry and refers to the abstract RSS portlet.

The portlets we'll build here all have the type instance, since they are self-contained.

The configuration file for portlets is jetspeed-config.jcfg in the WEB-INF/conf directory. Each user has two configuration files: homeHTML.psml and homeWML.psml. These files are stored in a different subdirectory -- WEB-INF/psml/<username> -- for each user. If you aren't logged into Jetspeed, you'll see the default user configuration. These default files, called default.psml and defaultWML.psml, are in WEB-INF/psml.

Let's add our new portlet to jetspeed-config.jcfg and then to the default user configuration file, default.psml.

In jetspeed-config.jcfg, there is a portlet registry section. You add a portlet entry to the end of the registry. The portlet has an instance type, and the class name is FirstPortlet. You can also add additional information, like any title and description you'd like; Jetspeed uses the title in the customization screen shown to the user.

The default configuration file contains the layout of all the portlets. They are organized under portlet controllers, which determine how the portlets appear on the page. Portlets may also have controls that modify the portlet's look and feel. Our portlet belongs to a RowColumnPortletController, which is similar to an HTML table, except that it can be either row- or column-oriented. We won't use any portlet controls, because Jetspeed's default control works fine.

Add <entry type="ref" parent="FirstPortlet" /> to the RowColumnPortletController that contains the JetspeedContent portlet entry. I added mine under JetspeedContent, which is how it will display to the user.

Restart Tomcat and go to http://localhost:8080/jetspeed in your Web browser. You should see your first portlet display under the Jetspeed links. If it doesn't appear, check your Tomcat console for error messages. When Tomcat can't find your portlet in the classpath, it puts an error on the console.

Wireless support

Jetspeed supports WML 1.1 and 1.2. To access Jetspeed from a Wireless Access Protocol (WAP) device, such as a mobile phone, you must set up a separate WAP gateway server, such as Nokia's Activ Server.

Jetspeed's WML support comes from the Apache ECS Project. A Java object in the org.apache.ecs.wml package represents each WML element, such as <card> or <p>. You'll have to write separate display code for HTML and WML if you intend to support both in your portlet; Jetspeed unfortunately doesn't have a built-in Web-to-wireless translator yet.

I did my wireless development with Nokia's WAP Toolkit (see Resources). This is one of two toolkits that has been tested with Jetspeed, the other being OpenWave's (formerly Phone.com) Software Developer Kit. Nokia's toolkit comes with a WAP gateway and a mobile phone simulator for running WAP/WML applications on your desktop computer.

Once you've installed the Nokia software, start Tomcat and the Nokia WAP Toolkit. For development purposes, you don't need the Nokia WAP gateway running. Log into Jetspeed running on your local machine at http://localhost:8080/jetspeed and play around with the portal on the simulated mobile phone. Anyone with a Web browser on his or her cell phone can access our first portlet.

Jetspeed supports several different output languages: HTML, WML, XHTML, and XML. Jetspeed queries the portlet to determine if it supports the user's MIME type. You must override the supportsType() method on AbstractPortlet to return true for both HTML and XML. Jetspeed can collect the current user's browser type from the Turbine runtime data to generate a ClientCapabilityMap object from a CapabilityMapFactory. The ClientCapabilityMap object provides methods for determining what capabilities the user's browser has by implementing the CapabilityMap interface. Browsers may accept more than one MIME type, so you can ask the ClientCapabilityMap what the preferred MIME type is.

You then set up different display code for WML and HTML inside the portlet. The ECS code for building WML works exactly the same way as the code for HTML. I used "On wireless" as the content for my portlet; you can use stock quotes, emails, or anything else. Here is the modified portlet, with built-in WML support. I've renamed the class WMLPortlet:

import org.apache.ecs.*;
import org.apache.ecs.html.*;
import org.apache.ecs.wml.P;
import org.apache.jetspeed.capability.CapabilityMap;
import org.apache.jetspeed.capability.CapabilityMapFactory;
import org.apache.jetspeed.portal.portlets.AbstractPortlet;
import org.apache.jetspeed.util.MimeType;
import org.apache.turbine.util.RunData;
import java.io.*;
public class WMLPortlet extends AbstractPortlet {
   //which MIME types does this portlet support
   public boolean supportsType (MimeType clientMimeType) {
      if (MimeType.HTML.equals(clientMimeType)) {
         return true;
      }
      if (MimeType.WML.equals(clientMimeType)) {
         return true;
      }
      return false;
   }
   public ConcreteElement getContent (RunData runData) {
      //create an ECS container for our content
      ElementContainer container = new ElementContainer();
      //get user's browser info from the Turbine runtime data.
      CapabilityMap capMap = CapabilityMapFactory.getCapabilityMap (runData);
      //show HTML code for the web
      if (capMap.getPreferredType().equals (MimeType.HTML)) {
         Table table = new Table()
                                 .setBorder(0)
                                 .addElement(new TR()
                                    .addElement(new TD()
                                       .addElement(new B()
                                          .addElement("I am bold."))))
                                 .addElement(new TR()
                                     .addElement(new TD()
                                        .addElement("I'm the lower table row.")));
         container.addElement(table);
      }
      //show WML code for wireless
      else if (capMap.getPreferredType().equals (MimeType.WML)) {
         org.apache.ecs.wml.P p = new org.apache.ecs.wml.P()
                                  .addElement("On wireless");
         container.addElement(p);
      }
      return container;
   }
}

Now you add the WMLPortlet portlet entry in jetspeed-config.jcfg, exactly as you did for FirstPortlet. You must add WMLPortlet to the defaultWML.psml wireless configuration file, the same way you added FirstPortlet to the default Web configuration file. Restart Tomcat and load Jetspeed in your mobile phone simulator. You should see something similar to the figure below.

Wireless Jetspeed portal

Web services

Web services seem to be the next big trend in enterprise software. Companies like Microsoft, IBM, Sun, and Hewlett-Packard are investing heavily in Web services toolkits for developers. Web services are a way to call procedures on remote machines using existing Web technologies, like HTTP and XML. SOAP is the XML format used to send queries and receive responses. Unlike previous remote procedure call technologies, this is a human-readable format. More than 50 implementations of SOAP are available, covering most languages and platforms. The XML Apache Project has released Apache SOAP 2.2, based on IBM's SOAP4j technology.

Web services don't have any graphical user interface (GUI) or frontend, which is why Jetspeed is a perfect match for them. You can create Web services without having to worry about the presentation layer and then build simple portlets to interface with your Web services. Jetspeed doesn't have any built-in support for browsing or accessing Web services yet, but I will show you how to call some publicly available Web services from a portlet.

To illustrate, we'll create a new portlet that quotes prices from Barnesandnoble.com. Xmethods.net hosts this Web service for anyone to use. Given an ISBN number, you can get a string that contains a book price. In addition, Xmethods.net has a list of publicly available Web services that you can use for testing. These services resemble the book pricing Web service. To use another service, you'll need to change the name of the remote object, the method called, the parameters, and the URL of the SOAP endpoint or router.

To run this example, you must download and install Apache SOAP 2.2, and then copy the soap.jar file into WEB-INF/lib. Compile the portlet with soap.jar in the classpath. Modify jetspeed-config.jcfg and default.psml to register the BookPricePortlet, and display it on the default user screen, similar to FirstPortlet:

import org.apache.ecs.*;
import org.apache.ecs.html.*;
import org.apache.jetspeed.portal.portlets.AbstractPortlet;
import org.apache.turbine.util.RunData;
import org.apache.soap.util.xml.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;
import java.io.*;
import java.net.*;
import java.util.Vector;
public class BookPricePortlet extends AbstractPortlet {
   public String getPrice(String isbn) {
      URL url = null;
      try {
         url=new URL("http://services.xmethods.com:80/soap/servlet/rpcrouter");
      } catch (MalformedURLException mue) {
         return mue.getMessage();
              }
              // This is the main SOAP object
      Call soapCall = new Call();
      // Use SOAP encoding
      soapCall.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
      // This is the remote object we're asking for the price
      soapCall.setTargetObjectURI("urn:xmethods-BNPriceCheck");
      // This is the name of the method on the above object
      soapCall.setMethodName("getPrice");
      // We need to send the ISBN number as an input parameter to the method
      Vector soapParams = new Vector();
      // name, type, value, encoding style
      Parameter isbnParam = new Parameter("isbn", String.class, isbn, null);
      soapParams.addElement(isbnParam);
      soapCall.setParams(soapParams);
      try {
         // Invoke the remote method on the object
         Response soapResponse = soapCall.invoke(url,"");
         // Check to see if there is an error, return "N/A"
         if (soapResponse.generatedFault()) {
            return "N/A";
         } else {
            // read result
            Parameter soapResult = soapResponse.getReturnValue ();
            // get a string from the result
            return soapResult.getValue().toString();
         }
      } catch (SOAPException se) {
         return se.getMessage();
      }
   }
   public ConcreteElement getContent(RunData runData) {
   StringElement price = new StringElement();
   price.addElement("Java Servlet Programming: " + getPrice("0596000405"));
   return price;
   }
}

Apparently, from the many problems presented on the Apache SOAP mailing list, Tomcat doesn't work well with Apache SOAP. Some versions of Tomcat come bundled with the XML parser from Sun's JAXP (Java API for XML Parsing) reference implementation. The Sun XML parser does not support namespaces, which is an XML feature that Apache SOAP requires. To run the example, you have to swap the Apache Xerces XML parser in for the Sun XML parser in the Tomcat classpath. The Sun XML parser is called parser.jar and is before Jetspeed's xerces.jar in the classpath. You have to use a recent version of Xerces, such as the one bundled with Jetspeed, and then edit your Tomcat.bat or Tomcat.sh file to put xerces.jar ahead of parser.jar in the classpath. I added xerces.jar to this line in tomcat.bat:

rem ----- Set Up The Runtime Classpath --------------------------------------
:setClasspath
set CP=%TOMCAT_HOME%\classes;%TOMCAT_HOME%\webapps\jetspeed\WEB-INF\lib\xerces.jar

Speed ahead

Jetspeed is the leading open source portal server. By creating portlets to integrate with external systems, you can give your users an integrated and customizable look and feel. Jetspeed is a good match for developing applications for both mobile devices and Web browsers. Web services can provide the backend for your portlets by using SOAP to communicate.

Jeff Linwood is CTO of Green Ninja, a software company based in Austin, Texas, that develops XML caching and database software for Web services. Previously, he was developer and lead developer on several high-end e-commerce projects using Java and XML for the Fortune 500 at Trilogy and pcOrder. Jeff has more than six years of Web application development experience.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies