Writing a channel for uPortal 2.0.1
(updated 8-06-02)
This document pertains to the uPortal, an open, sharable internet portal under development by institutions of higher-education.
The purpose of this document is to describe the steps required to write a custom uPortal channel. It details each of the main steps, simultaneously building a simple example channel. When referring to the example channel, the background is this color.
I wrote it all by hand, using vim.
Help is always appreciated! If you find something wrong with this document, or would like to add something here or there, or anything of the sort, send me an email. Feel free to edit the html source and send me a modification, if you like. Just please make sure that it's still valid xhtml.
uPortal 2.0.1 supports six types of channels. These are:
Channel Type | Description | What you must provide |
---|---|---|
Image | This is a simple channel type that is used to display an image with optional caption and subcaption. The image may be a hyperlink to another page. | Image url, hyperlink, optional caption and subcaption |
Inline Frame | This type of channel is used to render an entire HTML page within a frame. It doesn't work with Internet Explorer versions less than 5 or Netscape versions less than 6, and it doesn't work very well with Netscape 6. | url of the html page |
RSS (Rich Site Summary) | This is the most common type of channel. All of the content is defined in an XML document, which must adhere to the RSS specification. | url of the rss file |
Simple XML Transformation | The channel's content is defined by an XML document, and the styling must be done with one or more XSL documents (written in XSLT). | url of the XML file and the XSL file |
Web Proxy | This is one way to incorporate an already-written web application. If you are creating a new channel (that you need to be dynamic), however, it is recommended that you write a custom channel. There is more information about using the web proxy channel type here. | url of the web application |
Custom | This type of channel is used to publish channels that need to incorporate dynamic content. It is the most general and powerful type of channel. All of the other types of channels are subtypes of custom channels. | Java class, XSL stylesheet(s) |
Much uPortal content can (and should!) be developed using one of the first five types of channels. On the other hand, certain types of content, particularly dynamic content, requires more power than these methods have to offer. For this type of power, a custom channel is in order.
This document discusses the steps required to create a custom channel using the Java IChannel interface.
Here are the steps you need to follow when writing your channel for uPortal 2.0.1.
First thing to do is decide exactly what your channel will do. Since you have decided your channel should be a custom Java channel, your channel probably has dynamic content (meaning it changes based on user input).
Try to envision just the content (not the presentation) of the output of your channel expressed as an XML string. If you think there might be other situations where people would need to express similar content, you might try looking for a DTD or schema that suits your needs (one good place to look is xml.org's XML registry). Using a more general DTD or schema can be useful as your channel will be producing XML that you might be able to use outside of the uPortal as well as inside.
Our example channel will take in a name, and then display "Hello" to that
name. The content of the output of the channel is merely a name, so the
XML will look something like:
<name>Owen</name>Where "Owen" is the name we're trying to pass to the stylesheet. The channel will also have an "About this channel" page, which simply displays some text describing the channel. In this case, the content is that text that we need to display: <about>A bunch of text describing the channel</about> |
Your Java source should be in a file with the same name as your channel, except that it has a 'C' prepended to it. It goes in:
<uportal-home>/source/<package>/
Note that with the quick-start distribution,
<uportal-home>
refers to the
uPortal_2-0-1-quick-start/uPortal_rel-2-0-1 directory
,
and with the uPortal-only distribution it refers to the
uPortal_rel-2-0-1
directory.
Your stylesheet list is usually named the same as your Java source, except instead of having a .java extension it has a .ssl extension. It goes in:
<uportal-home>/webpages/stylesheets/<package>/
The stylesheets are .xsl files, and can be named whatever you choose. They go in the same directory as the stylesheet list file.
The example channel is called HelloWorld, so its Java source goes in
CHelloWorld.java. Since the package on the channel is: edu.virginia.uportal.channels.helloworldThe file locations for all the files that are used are as follows: <uportal-home>/source/edu/virginia/uportal/channels/helloworld/CHelloWorld.java <uportal-home>/webpages/stylesheets/edu/virginia/uportal/channels/helloworld/CHelloWorld.ssl <uportal-home>/webpages/stylesheets/edu/virginia/uportal/channels/helloworld/normal_explorer.xsl <uportal-home>/webpages/stylesheets/edu/virginia/uportal/channels/helloworld/normal_netscape.xsl <uportal-home>/webpages/stylesheets/edu/virginia/uportal/channels/helloworld/about.xsl |
There are some classes that are used by every channel for the uPortal. You'll need import statements for at least these classes.
// A channel needs these eight classes no matter what: import org.jasig.portal.IChannel; import org.jasig.portal.ChannelStaticData; import org.jasig.portal.ChannelRuntimeData; import org.jasig.portal.ChannelRuntimeProperties; import org.jasig.portal.PortalEvent; import org.jasig.portal.PortalException; import org.jasig.portal.utils.XSLT; import org.xml.sax.ContentHandler; // This is only useful if you will be using the LogService (which you should!!): import org.jasig.portal.services.LogService; |
Your class must implement the IChannel
interface, so this
must be in the class declaration.
public class CHelloWorld implements IChannel |
At the very least, you'll need to keep track of the static data and the runtime data that the portal sends you.
private ChannelStaticData staticData; private ChannelRuntimeData runtimeData; |
Often your channel consists of a few different "views" or "pages." One good way to deal with this is to enumerate the different pages and have some state for remember the current page.
private static final int NORMAL_MODE = 42; // two of my private static final int ABOUT_MODE = 31337; // favorite numbers private int mode; // whether we're in NORMAL_MODE or ABOUT_MODE |
You'll also need fields to keep track of any other state that you need for your channel.
private String name; // the name to say hello to private String name_prev; // the name that was previously submitted, to go // in the text box by default. |
You can do any initialization you need in the constructor. It will get called once as the uPortal is setting up your channel.
/** * Construct0r. */ public CHelloWorld() { this.name = "World"; // default to "Hello World!" this.name_prev = ""; // start with the text box empty this.mode = NORMAL_MODE; // start in normal mode } |
The IChannel interface requires that you implement five methods. These are described below.
public ChannelRuntimeProperties getRuntimeProperties()
The purpose of this method is to provide a way for your channel to
communicate runtime information to the portal. You are supposed to
construct an object of type ChannelRuntimeProperties
and
return it.
However, with the current implementation, literally nothing is done with this object once you return it, so there isn't much to do but return a new object.
/** * Returns channel runtime properties. * Satisfies implementation of Channel Interface. * * @return handle to runtime properties */ public ChannelRuntimeProperties getRuntimeProperties() { return new ChannelRuntimeProperties(); } |
public void receiveEvent(PortalEvent ev)
This method is called when the user initiates some sort of layout event, for example they click on the 'detach' button, or the 'help' button. Note that whether these buttons will even be present is specified when you publish the channel.
Events are stored as both names and numbers. If ev
were
an event you received, you could get the name like this:
String eventName = ev.getEventName();
And you could get the number like this:
int eventNumber = ev.getEventNumber();
The types of events that can be passed to receiveEvent are:
event number | event name |
---|---|
PortalEvent.RENDERING_DONE |
"renderingDone" |
PortalEvent.SESSION_DONE |
"sessionDone" |
PortalEvent.UNSUBSCRIBE |
"unsubscribe" |
PortalEvent.EDIT_BUTTON_EVENT |
"editButtonEvent" |
PortalEvent.HELP_BUTTON_EVENT |
"helpButtonEvent" |
PortalEvent.ABOUT_BUTTON_EVENT |
"aboutButtonEvent" |
PortalEvent.DETACH_BUTTON_EVENT |
"detachButtonEvent" |
If you are using any of these events, this is the place for the code to react to the event.
In CHelloWorld, we will be responding to ABOUT_BUTTON_EVENT by
switching to ABOUT_MODE.
/** * Process layout-level events coming from the portal. * Satisfies implementation of IChannel Interface. * * @param PortalEvent ev a portal layout event */ public void receiveEvent(PortalEvent ev) { if (ev.getEventNumber() == PortalEvent.ABOUT_BUTTON_EVENT) { mode = ABOUT_MODE; } } |
public void setStaticData(ChannelStaticData sd)
This method is used by the uPortal to pass in static data to your channel. Objects of type ChannelStaticData contain channel configuration items and parameters, including things like the channel's publish-time id, the channel's subscribe-time id, the IPerson object containing information on the user viewing your channel, and the channel's timeout in milliseconds.
Usually, all that needs to be done in this method is to store the ChannelStaticData object in a field.
/** * Receive static channel data from the portal. * Satisfies implementation of IChannel Interface. * * @param ChannelStaticData sd static channel data */ public void setStaticData(ChannelStaticData sd) { this.staticData = sd; } |
public void setRuntimeData(ChannelRuntimeData rd)
This method is called every time the portal is about to ask your
channel to render (which happens every time the page with your channel
is rendered). You are passed in a ChannelRuntimeData
object, which you should store in a field for use later.
Usually any form processing that needs to be done is done here, as the http request variables come in with the ChannelRuntimeData.
/** * Receive channel runtime data from the portal. * Satisfies implementation of IChannel Interface. * * @param ChannelRuntimeData rd handle to channel runtime data */ public void setRuntimeData(ChannelRuntimeData rd) { // Most of the processing is usually done here. this.runtimeData = rd; // process the form submissions if (runtimeData.getParameter("submit") != null) { name = runtimeData.getParameter("name"); name_prev = name; } if (runtimeData.getParameter("clear") != null) { name_prev = ""; } if (runtimeData.getParameter("back") != null) { mode = NORMAL_MODE; } } |
public void renderXML(ContentHandler out) throws PortalException
This is where you produce the XML that will be the contents of your
page. Most of the work is done using an XSLT
object, which
you construct and then use to set parameters for rendering the XML.
Typical usage of the XSLT class looks like this:
XSLT xslt = new XSLT(this); xslt.setXML("myXMLDoc.xml"); xslt.setSSL("myChannel.ssl", "aTitle", runtimeData.getBrowserInfo()); xslt.setTarget(out); xslt.setStylesheetParameter("param1Name", "param1Value"); xslt.setStylesheetParameter("param2Name", "param2Value"); xslt.transform();
Through setSSL
, you tell XSLT where your SSL file is,
the title of the stylesheet you want to use, and the browser information
that comes from the client. Based on this information it selects
which stylesheet to use for rendering your XML.
/** Output channel content to the portal * @param out a sax document handler */ public void renderXML(ContentHandler out) throws PortalException { String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; String stylesheet = "normal"; if (mode == NORMAL_MODE) { xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<name>"+name+"</name>"; stylesheet = "normal"; } else if (mode == ABOUT_MODE) { xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<about channel=\"Hello World\">This channel was created " + "for demonstrative purposes, by Owen Gunden <nog7g@" + "virginia.edu></about>"; stylesheet = "about"; } // Create a new XSLT styling engine XSLT xslt = new XSLT(this); // pass the result XML to the styling engine. xslt.setXML(xml); // specify the stylesheet selector xslt.setXSL("CHelloWorld.ssl", stylesheet, runtimeData.getBrowserInfo()); // set parameters that the stylesheet needs. xslt.setStylesheetParameter("baseActionURL", runtimeData.getBaseActionURL()); xslt.setStylesheetParameter("name_prev", name_prev); // set the output Handler for the output. xslt.setTarget(out); // do the deed xslt.transform(); } |
The stylesheet list is just that: a list of stylesheets. You name a
bunch of stylesheets and then describe the cases in which they should be
used. The attributes of stylesheets which differentiate them from one
another are the stylesheet title
and the stylesheet
media
. The title is arbitrary, the media refers to the user's
browser type.
The stylesheet list is an XML document containing a processing
instruction (the thing inside the <? ?>
tags) for
each stylesheet. Since it is an XML document, you must have beginning
and ending document
tags, even though they mean nothing in
this context.
<?xml version="1.0"?> <!-- CHelloWorld.ssl, part of the HelloWorld example channel --> <?xml-stylesheet href="normal_netscape.xsl" title="normal" type="text/xsl" media="netscape"?> <?xml-stylesheet href="normal_explorer.xsl" title="normal" type="text/xsl" media="explorer"?> <?xml-stylesheet href="about.xsl" title="about" type="text/xsl" default="true"?> <document> </document> |
Your stylesheets are written in XSLT, which is a language for transforming XML documents.
See the section What you need to know for information on where to learn more about XML/XSLT.
about.xsl |
<?xml version="1.0" encoding="utf-8"?> <!-- about.xsl, part of the HelloWorld example channel --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="no" /> <xsl:param name="baseActionURL">baseActionURL_false</xsl:param> <xsl:param name="name_prev">world</xsl:param> <xsl:template match="/"> <a name="helloworld" /> <xsl:apply-templates /> <p><a href="{$baseActionURL}?back=true">Back</a></p> </xsl:template> <xsl:template match="about"> <p><xsl:value-of select="." /></p> </xsl:template> </xsl:stylesheet> |
normal_explorer.xsl |
<?xml version="1.0" encoding="utf-8"?> <!-- normal_explorer.xsl, part of the HelloWorld example channel --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="no" /> <xsl:param name="baseActionURL">baseActionURL_false</xsl:param> <xsl:param name="name_prev">world</xsl:param> <xsl:template match="/"> <p /> <a name="helloworld" /> <xsl:apply-templates /> <form action="{$baseActionURL}#helloworld" method="post"> Enter your name: <input type="text" name="name" size="15" class="uportal-input-text" value="{$name_prev}" /> <input type="submit" name="submit" value="submit" class="uportal-button" /> <input type="submit" name="clear" value="clear" class="uportal-button" /> </form> <p>This is the <b>explorer</b> stylesheet.</p> </xsl:template> <xsl:template match="name"> <p>Hello <xsl:value-of select="." />!</p> </xsl:template> </xsl:stylesheet> |
normal_netscape.xsl |
<?xml version="1.0" encoding="utf-8"?> <!-- normal_netscape.xsl, part of the HelloWorld example channel --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="no" /> <xsl:param name="baseActionURL">baseActionURL_false</xsl:param> <xsl:param name="name_prev">world</xsl:param> <xsl:template match="/"> <a name="helloworld" /> <xsl:apply-templates /> <form action="{$baseActionURL}#helloworld" method="post"> Enter your name: <input type="text" name="name" size="15" class="uportal-input-text" value="{$name_prev}" /> <input type="submit" name="submit" value="submit" class="uportal-button" /> <input type="submit" name="clear" value="clear" class="uportal-button" /> <p>This is the <b>netscape</b> stylesheet.</p> </form> </xsl:template> <xsl:template match="name"> <p>Hello <xsl:value-of select="." />!</p> </xsl:template> </xsl:stylesheet> |
Before you can try out your new channel, you need to publish it to the uPortal. This means putting information about the channel into the uPortal database.
Fortunately, there is a mechanism built in to the uPortal to help you publish a channel. In order to use this mechanism, however, you need to be a privelaged uPortal user (to be precise, you need to belong to the "channel developers" group). If you aren't a privelaged user, get someone who is in the Portal Administrators group (or log in as such a user) and add your username using the groups manager. If this method doesn't work, or you don't know of any privelaged users, add an entry to the up_group_membership table in the database. The SQL to add this entry would look something like this:
INSERT INTO up_group_membership VALUES (14, 501, 'F')
where 14 is the id of the group you want to join (groups and their ids are listed in up_group) and 501 is your uPortal user id (users and their ids are listed in up_user).
Having logged in as someone with the proper permissions, you should see a link in the top-right corner of the page that reads "Channel Admin." Click it, and follow the instructions to publish your channel.
Here are the steps used to publish HelloWorld:
|