First Previous Next Last Portal Guide  

CHAPTER 7    Developing Components

This chapter describes basic techniques for writing portal components. It includes:

 
Top of page

About components

Components generate dynamic content that appears in the context of a portal page. When a page is requested at runtime, the Presentation Manager combines the content from the components on the page and renders it for viewing. Several components are usually invoked in a single request for a portal page.

Component content can come from many different sources, including the Content Management subsystem, databases, other Web sites, and Web services. Component content can include text, images, links, forms, and any other output that can be shown in a Web page.

A component can change its content and presentation based on current conditions, user preferences, profiles, security roles, and interaction with other components. You can supply business rules that are installed in the portal and invoked in your component to process the content.

Most components use HTML or XML to present data to the user, but some components are nonvisual and therefore do not display data to the user. The layout of the result depends on the browser, the HTML tags, or the XSL style sheet you associate with the component.

A sophisticated component should be designed for fast performance by efficiently retrieving only the data it needs and caching it when appropriate.

 
Top of page

Component implementation

You can create a portal component with the Portal Component Wizard, which is available in exteNd Workbench. See Creating Custom Components.

A component requires two files:

The component class does the following:

  1. Imports appropriate Director API. packages

  2. Implements the interface com.sssw.portal.api.EbiPortalComponent.

    NOTE:   The EbiPortalComponent2 interface used in ePortal Version 2 is still supported for backward compatibility—but is not recommended for new development.

The EbiPortalComponent interface declares three methods in which you specify your component's unique behavior:

Each of these methods is discussed in detail below.

 
Top of section

Component arguments

All three methods accept the following two arguments:

 
Top of section

initialize()

This method initializes a component.

For a portal-lifetime component, initialize() is called once when the component is instantiated. (A portal-lifetime component remains instantiated to respond to many requests.)

For a request-lifetime component, initialize() is called every time the component is accessed. (An instance of a request-lifetime component is created every time it is called.)

Example:

  public void initialize(
          com.sssw.portal.api.EbiPortalContext context, 
          java.util.Map params ) 
          throws com.sssw.fw.exception.EboUnrecoverableSystemException 
  {
          // obtain the log from the factory
          //it is maintained as a singleton.
          log = com.sssw.fw.log.EboLogFactory.getLog( "COMPONENT" );
          // only log items if the log level indicates we should
          if ( log.isTrace() ) {
              log.trace("MyComponent1 in initialize method" );
          }
  }

 
Top of section

getComponentData()

Generates HTML or XML content (see Generating component content), then calls the following methods in EbiPortalContext to send the content to the Presentation Manager:

Example:

  public void getComponentData( 
          com.sssw.portal.api.EbiPortalContext context, 
          java.util.Map params ) 
          throws com.sssw.fw.exception.EboUnrecoverableSystemException {
          // only log items if the log level indicates we should
          if ( log.isTrace() ) {
                  log.trace( "MyComponent1 in getComponentData method" );
          }
          StringBuffer sb = new StringBuffer();
          // set the type of data being returned, html, xml, dom, non-visual
          context.setContentType(                 com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_HTML_UTF8 );
          // build up the component return data, buffers are best
          sb.append("MyComponent1 data goes here" );
          // place the content into the context
          context.setComponentContent( sb.toString() );
          // only log items if the log level indicates we should
          if ( log.isTrace() ) {
                  log.trace( "MyComponent1 returned component content => " +
                  context.getComponentContent() );
          }
  }

 
Top of section

processRequest()

Saves user selections to be reused the next time the component itself or other components are displayed. Save the information by setting temporary or session values in the EbiPortalContext object. Temporary values are available for the duration of the request; session values persist through the session.

Example:

  public void processRequest(
          com.sssw.portal.api.EbiPortalContext context, 
          java.util.Map params) 
          throws com.sssw.fw.exception.EboUnrecoverableSystemException {
          // only log items if the log level indicates we should
          if ( log.isTrace() ) {
                  log.trace( "MyComponent1 in processRequest method" );
          }
          //process incoming items on the request object
          //String parm = context.getEbiRequest().getParameter("parm-name");
  }

 
Top of page

About component descriptors

Each portal component must have at least one XML descriptor. You can use a single component class with multiple descriptors. This allows you to use a component's output in many different ways.

Component descriptor files must be located in the portal-component directory within a resource set. When you create a component by using the Portal Component Wizard in Workbench, you specify the target resource set. The wizard then creates a descriptor for you automatically and adds it to the portal-component directory.

For more information    For more information about where files are located in a resource set, see the section on Subdirectories for resources and Java classes in the Core Development Guide.

Here's an example of a descriptor generated by the Portal Component Wizard:

  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE portal-component PUBLIC "-//SilverStream Software, LLC//DTD Portal Component 4.0//EN" "portal-component_4_0.dtd">
  <portal-component>
  <class-name>UntitledComponent1</class-name>
  <display-name>UntitledComponent1</display-name>
  <description>UntitledComponent1 Component</description>
  <lifetime>true</lifetime>
  <enable-title-bar>true</enable-title-bar>
  <component-styles/>
  <categories/>
  </portal-component>

You can modify the settings to suit the requirements of your application. You can also create additional descriptors for a particular component implementation. For example, you might want to provide several versions of the same component, each with its own default value settings.

For more information    For information about editing component descriptors, see Editing a component descriptor.

 
Top of page

Displaying a component

To display a component in a page, you specify the component's ID. The ID of a component is the name given to the XML descriptor file (without the extension) that describes the component. For example, if the XML file name for a component is MyComponent1.xml, the component ID is MyComponent1.

Here's how you would specify the ID in the s3-component tag on a PID page:

  <s3-component id="MyComponent1" instance="MyInstance" />

Here's how you would specify the ID in the displayComponent tag in a JSP page:

  <%@ taglib uri="/portal" prefix="ep" %>
  ...
  <ep:displayComponent compID="MyComponent1" name="MyComponent1" />

 
Top of page

Generating component content

In your component's getComponentData() method, you:

  1. Generate content to be displayed in a Web page

  2. Call EbiPortalContext.setContentType() to set the content type to HTML or XML

  3. Call EbiPortalContext.setComponentContent() to send the content to the Presentation Manager

 
Top of section

Setting the content type

You specify whether you are using HTML or XML by setting the content type in the EbiPortalContext object. The content type determines what type of object to specify for the setComponentContent() method.

For example, you might set the content type and content with code like this, where the variable xml is a StringBuffer:

  context.setContentType( EbiComponentConstants.MIME_TYPE_XML );
  context.setComponentContent(xml.toString());

The interface com.sssw.portal.api.EbiComponentConstants defines constants for content MIME types:

Content type constant

Format for content

MIME_TYPE_HTML

A string containing a portion of an HTML page. The HTML can be anything that would appear within the BODY element. It does not include the BODY element.

MIME_TYPE_HTML_UTF8

A string containing a portion of an HTML page that uses UTF-8 encoding.

MIME_TYPE_WML

A string containing WML-tagged data.

MIME_TYPE_XMLDOM

An object that implements the org.w3c.dom.Document interface.

MIME_TYPE_XML

A string containing XML-tagged data.

MIME_TYPE_NONVISUAL

A nonvisual object that performs background processing. A nonvisual object does not appear on the page.

The Portal Component Wizard adds several setContentType() method calls to the generated component, commenting out all but the one that sets the type according to your preference.

  //context.setContentType( com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_NONVISUAL );
  //context.setContentType( com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_XML );
  //context.setContentType( com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_XMLDOM );
  //context.setContentType( com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_HTML );
  context.setContentType( com.sssw.portal.api.EbiComponentConstants.MIME_TYPE_HTML_UTF8 );

You can change the content type for a component by editing the generated code.

 
Top of section

Generating HTML content

A component generates HTML content that is inserted into an HTML page. That means the content it generates should be a piece of a page, not a full page with HTML and BODY elements.

To generate your component's interface in HTML, you gather the information and generate a string that contains the formatting tags and content, plus scripts for client-side processing if desired.

Example of HTML generation

This code illustrates how to build a simple HTML string that is passed back to the portal page:

  StringBuffer sb = new StringBuffer();
  sb.append("<h2>My Component</h2>");
  sb.append("<p>This component presents information.</p>");
  
  context.setContentType( EbiComponentConstants.MIME_TYPE_HTML );
  context.setComponentContent( sb.toString());

 
Top of section

Generating XML content

In the component's getComponentData() method, you specify the XML MIME type (defined in the EbiComponentConstants interface), which tells Director to expect XML output. To generate your component's interface in XML, you gather your content and build a structure of XML elements. The structure and element names are up to you.

There are two ways to approach XML generation. You can:

The three subsections that follow provide examples of using these techniques to produce this simple XML:

  <?xml version="1.0"?>
  <!-- HEADLINES.XML -->
  
  <HEADLINES>
     <CATEGORY NAME="Sports" URL="http://www.sportsheadlines.com"/>
     <CATEGORY NAME="Music" URL="http://www.arts.com/musicnews"/>
     <CATEGORY NAME="Films" URL="http://www.arts.com/movienews"/>
  </HEADLINES>

Example of XML generation by encoding elements in a string

This code illustrates how to build a simple XML string that, like the HTML in the previous section, is passed back to the Presentation Manager via the EbiPortalContext object:

  StringBuffer xml = new StringBuffer(); 
  
  xml.append("<?xml version=\"1.0\"?>");
  xml.append("<!-- HEADLINES.XML -->");
  
  xml.append("<HEADLINES>\n" );
  xml.append("   <CATEGORY NAME=\"Sports\"" ); xml.append("      URL=\"http://www.sportsheadlines.com\"/>\n" );
  xml.append("   <CATEGORY NAME=\"Music\"" ); xml.append("      URL=\"http://www.arts.com/musicnews\"/>\n" );
  xml.append("   <CATEGORY NAME=\"Films\"" ); xml.append("      URL=\"http://www.arts.com/movienews\"/>\n" );
  xml.append("</HEADLINES>\n" );
  
  context.setContentType( EbiComponentConstants.MIME_TYPE_XML );
  context.setComponentContent( xml.toString());

Example of XML generation with a DOM

If you want to construct XML by building a DOM, you can use any XML API to handle the XML content. However, Director installs the open-source Apache Xerces API as well as the Apache Xalan style sheet translator, so it is a good choice for your own XML work. To use the Apache XML API in your own code, include some or all of these import statements:

  import org.apache.xalan.xslt.*;
  import org.apache.xerces.parsers.*;
  import org.apache.xml.serialize.*;
  import org.w3c.dom.*;
  import org.xml.sax.*;

This code illustrates how to build a simple DOM of the headline categories and pass it back to the Presentation Manager via the EbiPortalContext object:

  String[] names = { "Sports", "Music", "Films" };
  String[] urls = { "http://www.sportsheadlines.com", 
                    "http://www.arts.com/musicnews",
                    "http://www.arts.com/movienews" };
  
  DocumentImpl doc = new DocumentImpl(null);
  Element root = doc.createElement("HEADLINES");
  doc.appendChild(root);
  for (int i = 0; i < names.length; i++)
  {
     Element categ = doc.createElement("CATEGORY");
     categ.setAttribute("NAME", names[i]);
     categ.setAttribute("URL", urls[i]);
     root.appendChild(categ);
  }
  context.setContentType(ComponentConstants.MIME_TYPE_XMLDOM);
  context.setComponentContent(doc);

For more information    For more information about the Apache XML API, see the Apache Software Foundation Web site.

 
Top of page

Rendering XML content

Director uses a translator (XSLT) that renders each XML element in HTML for inclusion in a portal page. The translator converts XML to HTML according to an XSL style (see example below). Director uses the open-source Apache Xerces document parser and Apache Xalan style sheet translator to process the XML.

Example of an XSL style

This XSL displays each name in a separate paragraph and makes it a link:

  <?xml version="1.0"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <!-- HEADLINES.XSL -->
  
  <xsl:template match="/"><xsl:apply-templates/></xsl:template>
  
  <xsl:template match="HEADLINES">
   <H1>Today's Headlines</H1>
   <xsl:apply-templates />
  </xsl:template>
  
  <xsl:template match="CATEGORY">
   <P>
  <A>
  <xsl:attribute name="HREF"><xsl:value-of select="@URL"/></xsl:attribute>
  <xsl:value-of select="@NAME"/> 
  </A>
  </P>
  </xsl:template>
  </xsl:stylesheet>

 
Top of section

Component styles and portal styles

Director lets you define a set of component styles that apply to a particular component. Each component style specifies a name (a style ID) that maps to a portal style. The portal style defines one or more XSL files that will be used for translation.

The following extract from the component descriptor for the MultiStyle component shows how you might define several component styles for a component:

  <portal-component>
  <class-name>
    com.sssw.portal.component.multistyle.MultiStyleComponent
  </class-name>
  <display-name>MultiStyleComponent</display-name>
  <description>MultiStyle Component</description>
  <lifetime>true</lifetime>
  <enable-title-bar>false</enable-title-bar>
  <personalizable>false</personalizable>
  <component-styles>
    <component-style>
     <name>MultiStyle1</name>
    </component-style>
    <component-style>
     <name>MultiStyle2</name>
    </component-style>
  </component-styles>
  <default-style>MultiStyle2</default-style>
  </portal-component>

If you are using the Portal Component Wizard and you select XML as the component type, the wizard generates these files in addition to the component class and descriptor:

The portal style descriptor maps the style sheet to the Generic_HTML device; the style sheet is used to return HTML to the client device.

 
Top of section

User agents and device profiles

Each portal style definition specifies one or more user agents. Each user agent specifies how the style is rendered for a particular user environment by mapping a device profile to an XSL file.

Each user agent specifies a device name and a file name. The device name maps to a device profile that indicates which user environment the style sheet applies to. The file name specifies the name of the XSL style sheet that will be used for rendering.

A group of related style sheets can share a single portal style name. That means you can define several environment-specific versions of the same basic style and group them together with a single name. The Presentation Manager will then use the style sheet that applies to the target environment. The following extract from the portal style descriptor for MultiStyle1 shows how you would define several environment-specific versions of the same component style:

  <portal-style>
  <display-name>MultiStyle1</display-name>
  <description>MultiStyle1</description>
  <user-agents>
    <user-agent>
     <device-name>Generic_HTML</device-name>
     <file-name>MultiStyle1_IE.xsl</file-name>
    </user-agent>
    <!--
    <user-agent>
     <device-name>Netscape</device-name>
     <file-name>MultiStyle1_NS.xsl</file-name>
    </user-agent>
    -->
    </user-agents>
  </portal-style>

 
Top of section

Setting default component styles

When a component has more than one style associated with it, you can specify a default style for it. You can override the default style in a number of ways. To determine which XSL style sheet to use to interpret your XML, the Presentation Manager performs these tests:

  1. The Presentation Manager checks to see whether the component specified the component style to use in the getComponentData() method by calling setComponentStyleID() on the context object:

      if (queryString != null && queryString.equals("C")) {
        context.setComponentStyleID("MultiStyle1");
      }
    

    If so, this style is used. The Presentation Manager will automatically use the style sheet that applies to the target environment.

  2. If the component did not set the style programmatically, the Presentation Manager checks to see whether the current user has specified a style preference for this component. User preferences are typically specified in a component property sheet and stored in the PROFILEUSERPREFERENCES table. If a style preference is found, this style is used. The Presentation Manager will automatically use the style sheet that applies to the target environment.

  3. If no user preference is found in the PortalUserPreferences table, the Presentation Manager checks to see whether the XML generated by the component provides processing instructions that specify which style sheet to use. The xml-stylesheet processing instruction specifies the XSL file to used, as illustrated by the following example:

      <?xml-stylesheet href="style-ie.xsl" type="text/xsl" media="MSIE"?>
    

    If a processing instruction is found, the specified style sheet is used.

  4. If no processing instruction is found, the default style specified for the component in the component descriptor is used to process the XML.

      <default-style>PhoneListStyle</default-style>
    

    The Presentation Manager will automatically use the style sheet that applies to the target environment.

 
Top of page

Localizing the user interface

Portal components that generate XML can provide support for localization. To make this possible, the Director architecture includes a localization engine that allows you to render text that is locale-specific. For example, you might build a component that displays text strings in British English when the language setting for the client browser is English [United Kingdom], and French when the language setting for the client browser is French [France].

The localization engine is a Xalan extension class called EboLocalization that loads text from resource bundles. The ability to call methods of a Java class from within an XSL style sheet is a supported feature of the Xalan processor. The localization extension class takes advantage of this feature. To get the localized text values that will be returned with the component data, the style sheet used by the component calls a method on the extension class.

The EboLocalization class is in the FrameworkService.jar file.

Resource bundles are a feature of the standard Java API. You can store any object, string, number, or other data in a resource bundle. To create a resource bundle, you need to create either a Java class that extends ListResourceBundle or a properties file that contains static strings. For each locale you want to support, you provide a separate version of the resource bundle. The name for each resource bundle class or properties file you create must follow the standard Java naming standards.

For more information    For complete information on creating resource bundles, see the reference documentation for the ResourceBundle, ListResourceBundle, and PropertyResourceBundle classes in the Java standard API documentation. All of these classes are in the java.util package.

The resource bundles must be uploaded to the server along with your application. Once a resource bundle has been uploaded, you can direct the XSL processor to find the localized text in the resource bundle by using the localization extension.

Localization on IBM WebSphere and SilverStream® eXtend Application Server 4.0   Both WebSphere and the SilverStream eXtend Application Server 4.0 require that the EboLocalization class and the resource files be at the same level within an EAR. If you place the resource files within a WAR contained with the EAR, they will not be found on these servers — since EboLocalization is in the FrameworkService.jar file, which is maintained at the EAR level. To ensure that your resource files are found, you need to put them in a separate JAR and include this JAR at the EAR level within your project.

 
Top of section

Preparing your XSL

The XSL for a component that generates XML usually has the text for the HTML hardcoded into the XSL itself. For example, the XSL might include the following HTML:

  <table><tr>
    <td>Welcome to our Web Site!</td>
    <td>Please visit again.</td>
  </tr></table>

But when localization is required for a component, the text strings are not hardcoded into the XSL. Instead, they are managed in resource bundles (subclasses of ListResourceBundle or properties files).

Here is an example of a simple properties resource bundle:

  welcome=Welcome to our Web Site!
  closing=Please visit again.

Resource bundles contain key/value pairs, as shown in this example. The XSL accesses the string value that is appropriate for a particular locale by specifying its key.

Preparing to use the extension class

Before you can use the localization extension in an XSL style sheet, you must first define the extension to the processor engine—by adding some items to the xsl:stylesheet tag.

  <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:lxslt="http://xml.apache.org/xslt"
    xmlns:textReader="xalan://com.sssw.fw.extension.EboLocalization">
  <xsl:output method="html" encoding="ISO-8859-1"/>

You are now ready to make Java method calls to the extension class from your style sheet. First you need to call the init() method. The init() method takes two parameters—the resource bundle key and the locale. Although this method does not return a value, it is most easily called in the context of a select attribute. You can call the init() method at the style sheet level by creating a dummy variable:

  <xsl:variable name="dummy">
    <xsl:value-of select=
     "textReader:init('rsrc.KBDocumentAdd',string(/document/@locale))"/>
  </xsl:variable>
  <xsl:template match="document">
  <xsl:value-of select="$dummy"/>

NOTE:   The locale must be prepared in the XML generated by the component. For more information, see Preparing your XML.

The code required to set up the localization extension class will usually be the same in each style sheet that needs to use it. Therefore, to make it easier for XSL developers to use the localization extension, you could create a style sheet that contains the standard setup instructions described above and then include a reference to that style sheet in all other style sheets that need to call methods on the extension class.

Getting locale-specific text strings

You can now access the text values from any template using the getText() method. To do this, simply replace your variable references with method calls:

  <table><tr>
    <td><xsl:value-of select="textReader:getText('welcome')"/></td>
    <td><xsl:value-of select="textReader:getText('closing')"/></td>
  </tr></table>

You could also place the text in variable elements at the beginning of each template and then refer to the variables later.

 
Top of section

Preparing your XML

The Xalan processor operates entirely on the server and therefore has no notion of client locales. The component must therefore include the current client locale as part of its generated XML so that it can be placed in a variable and passed to the extension class. To do this, the component needs to check the locale on the client (by calling the getLocale() method on EbiContext) and then construct locale-specific XML that includes the locale. You can structure the XML in any way you like, as long as the structure matches the structure you specify in the call to the init() method.

For example, suppose the XSL contains the following call to the init() method:

  <xsl:variable name="dummy">
    <xsl:value-of select=
     "textReader:init('rsrc.KBDocumentAdd',string(/document/@locale))"/>
  </xsl:variable>

In this case, the component needs to generate XML that specifies the locale in the following manner:

  <document locale="en_US">
    etc...
  </document>

To do this, the component might use the following code to a construct an XML DOM that includes the locale:

  Document newDoc = new DocumentImpl();
  Element base = newDoc.createElement("document");
  ...
  base.setAttribute("locale",context.getLocale().toString());
  ...

 
Top of page

Setting component lifetime

Component lifetime (persistence) is one of the elements that you can set in the component descriptor.

A request-lifetime component services just one request. An instance exists for every session that calls it. For most components, there is no reason to use request-lifetime.

A portal-lifetime component persists for the duration of the portal. It is instantiated on the server the first time it is accessed and remains instantiated so that it can be accessed by many different portal sessions. You can implement the initialize() method to do tasks that are done the first time the component is accessed. For example, you might initialize variables or cache data.

Using portal-lifetime components makes your portal application scalable to a larger number of users, since a single object can service many sessions. For example, suppose a headline news component retrieves and caches all available headlines. When accessed by a user, it serves the headlines that meet the user's criteria. As a portal-lifetime component, it can periodically retrieve the headlines for everyone at a predetermined refresh interval, rather than for every user's request.

When you install the component, you can change its persistence setting—although if the component designer designated request-lifetime (false), it may not be safe to change it to true.

 
Top of section

Responding to users

A portal-lifetime component can respond to an individual user, because the EbiPortalContext object passed to the getComponentData() and processRequest() methods includes the portal session object.

By calling methods of EbiPortalContext, you can get user information, get or set temporary values stored in the request, and get and set session parameters used in inter- and intra-component interaction.

If you need direct access to methods of the session or the HTTP request, you can get them from EbiPortalContext:

  EbiSession session = context.getEbiSession();
  EbiRequest request = context.getEbiRequest();

 
Top of section

Writing a portal-lifetime component

The main requirement for a portal-lifetime component is that it be threadsafe. To design a threadsafe portal-lifetime component, you need to:

For more information    For more information on writing threadsafe code, see the Java documentation from Sun.

 
Top of page

Adding support for multiple component instances

You can add multiple instances of the same component to a single page. However, if the component has a form that submits data for processing, you need to add some code to your component to make this work. In particular, you need to store the form field values that were submitted. To do this, you set values in the EbiPortalContext object. The key you use to store the data needs to include the component instance name (the string specified in the instance attribute of the s3-component tag), as shown below.

  public void getComponentData(EbiPortalContext context, Map params)
  {
  ...
   context.setValue(context.getEbiRequest().getRequestURI() + "/" +
    context.getComponentInstanceName(),
    context.getEbiRequest().getParameter(LAST_NAME_FIELD));
  ...
  }

The getComponentData() method can retrieve the stored form field values and uses these values to generate separate result sets for any queries that must be performed.

  public void getComponentData(EbiPortalContext context, Map params)
  {
  ...
  String queryString = null;
   queryString = (String)   context.getValue(context.getEbiRequest().getRequestURI() + "/" +   context.getComponentInstanceName() );
  ...
  
   DocumentImpl doc = new DocumentImpl(null);
  
   // Call a method to build the query results as an XML tree
   queryPhoneList(context, queryString, doc);
  }

 
Top of page

Caching component data

Director maintains a server cache for data that a component could reuse. This cache can hold any type of object. The component can cache data over a component's lifetime or for the duration of a user's portal session. Cached items associated with a session are deleted when that session is done.

In the PAC, you can configure the maximum size for cached objects and the maximum size for the total cache. When the cache is full, the portal purges least-recently-used items.

The way you use the cache depends on the nature of the component's data and how often it changes.

 
Top of section

How to cache retrieved data

The component can cache data in the portal's server cache. The cached data can be associated with the session or be available to the component in any session. Data associated with the session is purged from the cache when the session ends. The keys to the cached data are the component ID and a content key that you assign.

What to cache   You should cache any data that can be reused to make the component more efficient.

The data a component displays usually comes from somewhere else, such as a database or another Web site. To make your portal application efficient, you probably don't want to get the data every time the portal redraws the component. Depending on the nature of the data, you may want to get new data after a period of time or when the user requests it. For example, if a component displays:

How to use the cache   After the component gets its data, your code can call the putObjectInCache() method to cache the data. There are two ways to call putObjectInCache():

The component can include a time in the cached information—for example, by putting the data in an array and making the time another element in the array. When the component generates its content in the getComponentData() method, it can check to see whether cached information is available and how old it is. If the data is current, the component can use it instead of spending time to retrieve it again from the original source.

Caching and a component's lifetime   For a portal-lifetime component, the data you cache with the portal's cache holder is available to every portal session that displays the component. A typical scenario is to cache all the data any user might request. Then each session can build a subset of content from the cache depending on the user's preferences. To cache the data for all users, call putObjectInCache() for the cache holder.

Caching and user preferences   If a user's preferences require complex processing of the data, you might want to cache data for individual users. These cache entries are associated with the user's session. You do this by calling putObjectInCache() for the session object.

When a user changes preferences for a customizable component, the cached data will be invalid. You will need some way of telling the component to discard the cache and reconstruct the content, such as having the property sheet delete the cache or setting a value in the portal session for the component to access.

Managing the cache time   These methods of EbiPortalContext provide a convenient way for a component to set and get the time that a component's data was last modified:

To do this

Use this method

Set the time to the current time

void setLastModifiedNow()

Set the time to a particular time

void setLastModified(long lastMod)

Get the time when content was most recently generated

long getLastModified()

You can use these methods to determine how and when to recreate data within a component. For example, you might want to get data from a newsfeed every 20 minutes. The first time the component was executed, you would get the data, convert it to an XML string, and store the string in the cache using the putObjectInCache() method. You would then set the last modified time to the current time. On subsequent requests, you would either get the cached XML (if getLastModified() indicated that the last update was less than 20 minutes ago), or go out to the Web and create the XML again (if getLastModified() showed that 20 minutes had passed since the last refresh).

Methods for saving and getting cache data

The methods for interacting with the cache are in the EbiCacheHolder class. You can call them for the cache holder object or for EbiSession, which extends EbiCacheHolder.

Here are the most useful versions of the cache methods:

The component key is an arbitrary string that is unique to the component. You can get the component name to use for the key via code like this:

  String mycomponent = 
    context.getComponentInfo().getComponentName();

Alternatively, you can get the class name for the component:

  String mycomponent = 
    context.getComponentInfo().getComponentClassName();

You can also get the name of a particular component instance and use that as the component key, like this:

  String mycompinstance =
    context.getComponentInstanceName();

The content key is another arbitrary string of your choosing that is meaningful to the component. By using different content keys, you can have multiple cache entries for the component.

You can store any type of object in the cache—for example, a String, an array of Strings, a hashtable, or an XML document. If your data is time-dependent, you can incorporate the date and time in the cached object so you know when to retrieve new data instead of using the cache.

Examples of caching retrieved data

These sample methods illustrate how to cache data in a component. In each example the component key is the class name. To make the cached data specific to each component instance, you might want to append the component instance name to the key as well.

Caching data for all users   Suppose the addCacheData() method in your component caches headlines from a particular newsfeed. The cached String array has two elements: the time and the headlines as XML data. The name of the newsfeed is the content key. The putObjectInCache() method is called on m_cache, a member variable of type EbiCacheHolder, so the cache entry is available to all sessions. The variable m_context is a member variable of type EbiPortalContext:

  private void addCacheData( String feed, String headlines )
  {
   String[] entry = new String[2];
   Long ltime = new Long( (new Date()).getTime() );
   entry[0] = ltime.toString();
   entry[1] = headlines;
   m_cache.putObjectInCache(
     m_context.getComponentInfo().getComponentClassName(), feed, entry );
  }
  

Getting the time   This method gets the time associated with the last cache entry for a particular newsfeed:

  private long getLastCachedTime(String feed)
  {
   try {
    String[] cacheEntry = 
     (String[])m_cache.getObjectInCache(m_context.getComponentInfo().
     getComponentClassName(),feed);
      if (cacheEntry == null)
        return 0;
      else
        return (new Long(cacheEntry[0])).longValue();
   } catch(Exception e){
      e.printStackTrace();
      return 0;
   }
  }

Caching data for a single session   This method is similar to addCacheData() above, but it associates the cached data with the session. The cache entry is available only in the current session:

  private void addCacheData(String feed, String headlines) 
     throws Exception
  {
     String[] entry = ...;	 // array of time and cached headlines
     String componentKey = 
        m_context.getComponentInfo().getComponentClassName();
     m_context.getEbiSession().putObjectInCache(componentKey, feed, entry);
  }

Retrieving data cached by the session   To retrieve the entry, this method calls getObjectInCache() for the session object:

  private Object getCacheItem( String feed)
  {
     String componentKey =
        m_context.getComponentInfo().getComponentClassName();
     Object cacheData = m_context.getEbiSession().
        getObjectInCache(componentKey, feed);
     return cacheData;
  }

 
Top of section

Alternatives to the managed cache

In addition to the server cache, Director supports other ways to store small amounts of data. You can store values:

You do these by calling methods of the EbiContext object.

Storing values for the current request

A request begins when the Presentation Manager calls processRequest() and ends when getComponentData() returns control to the Presentation Manager. You can store values for the duration of the request by calling setTemporaryValue(), a method of EbiContext. The key to the stored value is any object of your choosing. These temporary values are purged when the request ends.

Use temporary values for storing parameters you get in the processRequest() method or values you want to use in other methods of your component, or other components on a page.

This code gets a request parameter and uses it to set a temporary value. The key is stored in the constant USER_CHOICE:

  String userChoice =
     context.getEbiRequest().getParameter(USER_CHOICE);
  if (userChoice != null)
        context.setTemporaryValue(USER_CHOICE, userChoice);

This code gets the value:

  String choice = (String) context.getTemporaryValue(USER_CHOICE);

Storing values on the session whiteboard

The session whiteboard is a holding area for data that the component needs to keep available. You store values on the session whiteboard using setValue() and getValue(), inherited methods of EbiPortalContext. Values remain available for the duration of the user's session or until they are removed. The key to the stored value is an arbitrary content key of your choosing.

For efficiency, you should not use the whiteboard for storing large objects. The data is not purged until you remove it or the session ends, meaning that the session could become bloated and inefficient.

These statements illustrate how to set and get a value from the whiteboard:

  context.setValue("current_date", date);
  Date stored_date = (Date) context.getValue("current_date");

You can also supply a default value that is returned if no stored value is found:

  Date stored_date = (Date) context.getValue("current_date", 
     new Date());

To keep your component efficient, remove values that are no longer needed. This statement removes the value for the key current_date:

  context.removeValue("current_date");

This statement removes all whiteboard values:

  context.removeAllValues();

 
Top of page

Setting up interactions between components

Your component can pass information to other components on the same page or a different page by storing data in the context object via the setValue() and getValue() methods of EbiPortalContext. Each time a page is redrawn, the components on the page can check the values of parameters stored in the context and change their display. This is the way to make data submitted in an HTML form available to a component.

The process works like this:

Step

What happens

Submit form

When the user submits the component's form, data in the form's named input fields are passed as request parameters. The portal's Presentation Manager calls the component's processRequest() method.

Process the request

In the component's processRequest() method, you write code that gets request parameter values and stores the values in the context object.

Portal redraws page

In the component's getComponentData() method, you write code that gets values stored in the portal session and uses those values to render the component's content.

Other components on the page can also read the saved data and change their content too.

The rest of this section describes the coding essentials for each of these steps.

For more information    For a complete example of this technique, see com.sssw.portal.component.phonelist.PhoneListComponent.java in sample_components.jar.

 
Top of section

Writing the form to submit values to the component

To make data in the form available to the component, the form needs these parts:

The form uses the POST method to handle the field data.

You build the form in the component's getComponentData() method. Your code can use methods and constants to avoid hardcoding data that might change. For example, you can:

Example of a form that submits values to the component

A component that looks up information (such as a search or phone list lookup component) often has two parts: a form with an input field for the user to enter the search criteria and an area to display the results, which might be hidden until the component has some results.

This search component accepts a name or part of a name and looks up contact information in a database. Before the first search it looks like this:

pfPhoneListBefore

After a search, it shows the results below the form. The user can search again after looking at the results. If you typed K in the Last Name field, the component displays these results:

pfPhoneListAfter

The HTML that is sent to the browser has an action URL, an input field named fldLastName, and a Submit button. The HTML output looks like this:

  <form method="POST" action="MyPage?c=LookupComponent">
   <table cellpadding="1" cellspacing="1" border="0">
    <tr>
      <td width="70">Last Name:</td>
      <td width="35">
         <input type="text" name="fldLastName" size="30">
      </td>
      <td width="100">
         <input type="submit" value="Search" name="bnSubmit">
      </td>
    </tr>
   </table>
  </form>

The component class defines a constant for the name of the form field:

  private static final String LAST_NAME_FIELD = "fldLastName";

In the getComponentData() method, the statement that builds the action URL uses a constant for the parameter identifier and calls getCallingPage() for the target page and getComponentClassName() for the component ID:

  html.append("<form method=\"POST\" ");
  html.append("action=\"" + context.getCallingPage() + "?");
  html.append(EbiComponentConstants.PARAM_COMPONENTID + "=" 
          + context.getComponentInfo().getComponentName());
  html.append("\">\n");

When specifying the input field, the code uses the LAST_NAME_FIELD constant:

  html.append("    <input type=\"text\" name=\"" 
          + LAST_NAME_FIELD + "\" size=\"30\">\n");

 
Top of section

Saving parameter values in processRequest()

In the component's processRequest() method, you get parameters from the request and set values in the session.

The parameters of the request object come from each named input field on the form. For each value the user enters in an input field, there is a parameter with the name of that field. The values are strings.

You get parameter values with the getParameter() method:

  String s_lastName =
     context.getEbiRequest().getParameter("fldLastName");

You set values in the session by calling setValue(). You use a name of your choice for the content key. To avoid naming conflicts with other components, you could incorporate the name of the component in the key. The values can be any object type.

This code sets a parameter whose name is stored in the variable key:

  context.setValue(key, s_lastName);

Example of setting session parameters

This code for processRequest() sets a session parameter to a value from the form. The constant LAST_NAME_FIELD identifies the name used for both the form's input field and the request parameter. The key used for the session parameter includes the request URI:

  public void processRequest(EbiPortalContext context,
        java.util.Map params) 
  {
  ...
   context.setValue(context.getEbiRequest().getRequestURI(),
    context.getEbiRequest().getParameter(LAST_NAME_FIELD));
  ...
  }

 
Top of section

Using the saved parameters in getComponentData()

To use the parameters when this or another component generates its content, you access the session parameters in the component's getComponentData() method.

To retrieve session values, you use the getValue() method:

  Object value =
    context.getValue(key);

The component can use the value it finds to decide what to display. After using the value, you should remove the value so the component won't repeat the data retrieval.

  context.removeValue(key);

Any component on the portal page can use the parameter. A second component gets the parameter value by specifying the same content key used by the component that saved the parameter.

Example of getting a session parameter

In this code, the getValue() method gets the stored value whose key matches the request URI and the component instance name:

  String queryString = (String)
    context.getValue(context.getEbiRequest().getRequestURI());

Once you have retrieved the value into the variable queryString, a search component could use the data in a SQL WHERE clause to look up data in a database. The following code checks whether the variable queryString has a value and if so, calls a method that queries the database and returns the data as a formatted HTML string:

  if (queryString != null && !(queryString.equals(""))) 
  {
     html.append(queryPhoneList(request, queryString));
  }

After using the parameter, this code removes it:

  context.removeValue(context.getEbiRequest().getRequestURI());
    First Previous Next Last Portal Guide  

Copyright © 2000, 2001, 2002, 2003 SilverStream Software, LLC, a wholly owned subsidiary of Novell, Inc. All rights reserved.