uPortal Channel ARchives


Contents
What is a Channel ARchive?
How Are They Installed?
Do They Require Different Code?
What is a Deployment Descriptor?
How Do You Build Them?
What Does the Future Hold?


What is a Channel ARchive?

A Channel ARchive, or CAR, in its simplest form is just a java archive with a ".car" extension. They are created with the Jar utility provided with the Java SDK. CARs can contain all elements used for the construction and presentation of custom java channel types and their contents. A fully functional CAR is included here to illustrate their capability. (Note: Some browsers append ".zip" to this file upon downloading. Simply rename it to carlot.car.) This car contains a channel known as the CAR LOT. It shows what cars exist in the portal and shows their contents. It contains the source java file, the compiled class file, a stylesheet list file, an XSLT stylesheet, and two images that are served up to the browser from within the CAR.  

You must have uPortal 2.1 or later to use CARs. You can publish CAR LOT using the following values.


Property
Value
Channel Type
Custom Java
Channel Title,
Channel Name,
Channel Funtional Name,
Channel Description
CARLOT
Channel Timeout
4000
Channel Class
com.sct.pipeline.uportal.channels.carlot.Channel
Channel Controls
Has none.
Selected Categories
Choose as desired.
Selected Groups and/or People
Choose as desired.

In uPortal 2.2, CARs received the added capabilities of housing services and workers and being able to declare them using a deployment descriptor file. (See "What is a Deployment Descriptor?" later in this document.) A channel archive named deployTest.car containing a service, a worker, and an extension block can be obtained here. The deployTest.car archive does not contain a channel. Its sole purpose is to illustrate the power and use of the deployment descriptor support. You must have uPortal 2.2 or later for this archive to work. Upon installation and restarting the portal click on the following link to access the worker which will be automatically installed.

http://localhost:8080/uPortal/worker.com_sct_pipeline_uportal_channels_deployTest_HelloWorker.uP

This link assumes the portal is running on the local machine on port 8080. If it isn't, then replace the appropriate host and port in the URL. You'll notice that the returned page shows if the worker and service were installed and if the extension block of the deployment descriptor executed successfully.

Through these examples we see that CARs are containers that can house multiple channels, their resources, workers and services, and a deployment descriptor indicating what resources they contain so that in the case of workers and services the portal can instantiate and use them. CARs can be used to package up and deploy from a single channel, worker, or service to an entire channel application suite.

How Are They Installed?

By default the portal looks in /WEB-INF/cars or any of its subdirectories for channel archives. To install a CAR simply place it in that directory or its subdirectories and restart the portal. Upon start up, the portal scans the cars directory structure for all CARs and loads internal tables with paths to each of the resources available from the archives. It also processes all extension blocks declared in any deployment descriptors found in the archives. (See "What is a Deployment Descriptor?" later in this document.)

If the portal is deployed in a container that uses a WAR file for deployment that container may not allow access to the default car directory in the WEB-INF directory. If such is the case or if you simply want to place your CARs in some other location you can define the following property in the portal.properties file with a fully qualified path to the location of the directory that will house your channel archives.

org.jasig.portal.car.CarResources.directory

The name of a channel archive has no affect on its contents or the use of those contents by the portal. If two CARs aquired from different sources but containing different components need to be deployed in the same directory, then simply rename one or the other prior to installing in the directory. Alternatively, you could create subdirectories beneath the car directory based on the java package directories of the provider of the CAR. Then the CARs can be placed into the appropriate subdirectories with their names unchanged.

Do They Require Different Code?

Channels may require minor changes to be deployed in a CAR. For example, an image used in the channel and located in some subdirectory of the /media directory would most likely have an XSLT stylesheet with code like the following:

<xsl:variable name="mediaPath">media/org/jasig/portal/channels/Cbookmarks</xsl:variable>
  ...
<img src="{$mediaPath}/folder_delete.gif"/>
  ...

This would still be appropriate if the folder_delete.gif image were still located in the appropriate place beneath the media directory. But such an approach would negate one of the major goals of a CAR; namely to place all resources needed by a channel into the archive to prevent tight coupling between the stylesheet and a file located in some other directory. Such a deployment is susceptible to installation errors if the image is not located in the correct location or is subsequently deleted.

A better solution is to place the image in the CAR and modify the channel to obtain an appropriate URL for aquiring the image from the CAR. To do so typically requires calling one of three versions of a method in ChannelRuntimeData specifically designed to construct the base of this URL and then passing that value to the XSLT stylesheet for use in constructing the full URLs to the various images used by the channel.

ChannelRuntimeData has three methods to assist with constructing URLs appropriate for browsers to aquire resources out of channel archives. These are:

getBaseMediaURL( Object aChannelObject )
getBaseMediaURL( Class aChannelClass )
getBaseMediaURL( String resourcePath )

The first two can be passed some object or class that is known to only be deployed with the channel. Typically, this is the channel itself. These two methods then look to see whether or not the class of the object was loaded from a CAR. If it was then the method returns a URL base that can be used to aquire images from a CAR. If the class was not loaded from a CAR, then the method returns the traditional URL base path for images namely, "/media.". The format for the URL base for CAR-accessible images is similar in form to:

tag.idempotent.worker.carRsrc.uP?carRsrc=

As you can see the URL still needs a value appended indicating what car resource is desired. That value must be the path as found within the channel archive. For example, if you execute jar tf carlot.car you'll see that the car.jpg image has a path of com/sct/pipeline/uportal/channels/carlot/car.jpg. This would be the value to append to the base URL to allow the browser to aquire that image directly from carlot.car.

Note, the package relative directories are used not only for CAR LOT'S compiled class files but also for all of its resources. Like Java code in general all resources deployed in CARs should be placed in globally unique, package relative directories as specified in Java Language Specification, section 7.7. This is because all resources aquired from CARs share the same namespace. If CAR LOT didn't use package relative directories, then the path to its car.jpg image would consist only of the file name itself. The probability of contention between such a path in one CAR and an identical path to a similarly named but completely different image in another CAR would cause one of the two images to be served up in place of both and the other would never be accessible.

For stylesheet lists the XSLT stylesheets can be specified with either the full path to the stylesheet or a path relative to the location of the stylesheet list. For example, each of the following enties are valid and point to the same resource within the CAR, assuming that the stylesheet list file channel.ssl containing such entries had a path within the CAR of com/sct/pipeline/uportal/channels/carlot/channel.ssl. The first two are relative. The third has the full path specified.

<?xml-stylesheet title="CarListStyleSheet" href="stylesheets/channel.xsl">

<?xml-stylesheet title="CarListStyleSheet" href="../carlot/stylesheets/channel.xsl">

<?xml-stylesheet title="CarListStyleSheet" href="com/sct/pipeline/uportal/channels/carlot/stylesheets/channel.xsl">

Note that the parent directory traversal regular expression "../" was added in uPortal version 2.2. Similar full or relative paths can be used for an XSLT's xsl:include and xsl:import elements and for its document() method as shown below. Again, the first two are relative and the third is fully qualified.

<?xsl:variable name="palette" select="document('v2colors.xml')/*/color"/>

<?xsl:import href="../tree/view.xsl"/>

<?xsl:include href="com/sct/uportal/channels/accnt/edits.xsl"/>

If a channel needs to access other resources located in a CAR, these resources are available just as any other resource obtained from a class loader. However, the class loader provided by the channel archive infrastructure must be used. You can obtain a handle on this class loader by using the org.jasig.portal.car.CarResources class. Then you can ask for a resource using the fully qualified path within the CAR. In the following code snippet I am aquiring the bytes for the image housed in deployTest.car.

CarResources cRes = CarResources.getInstance();
ClassLoader cl = cRes.getClassLoader();
String img = "com.sct.pipeline.uportal.channels.deployTest.youveGotIt.jpg";
InputStream iStream = cl.getResourceAsStream( img );
// now read the stream of bytes.

Alternatively, if you have a class that was loaded from a CAR, you can use its getResource() and getResourceAsStream() methods because all classes delegate to the class loader by which they were loaded. Additionally, since the CAR class loader delegates first to the parent class loader before looking in CARs you can use this class loader for any resource expected on the classpath.

Finally, the org.jasig.portal.util.ResourceLoader class uses the CAR class loader. Therefore, this class can obtain resources from both the regular classpath and within CARs.

What is a Deployment Descriptor?

A deployment descriptor provides a mechanism whereby elements contained within the CAR can be conveyed to the portal and the portal can take action to access and use those elements. The deployment descriptor for a CAR has the name comp.xml. This name is case sensitive and must be located in the META-INF directory within a CAR. Performing jar tf deployTest.car on the deployTest CAR reveals the full path to its deployment descriptor conforming to this requirement: META-INF/comp.xml.

The deployment descriptor currently has the following general form for its document type.

<!ELEMENT component (description, worker, service, ext)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT ext (#PCDATA)>
<!ATTLIST ext contentHandler CDATA #REQUIRED>
<!ELEMENT service (#PCDATA)>
<!ELEMENT worker EMPTY>
<!ATTLIST worker class CDATA #REQUIRED>

The contents of the service and ext elements are opaque to the channel archive deployment descriptor processing code and are defined by the utility that uses those contents. Additionally, since comp.xml is an XML file and must be well formed, the contents of each of these blocks must also be well formed XML. An example of a deployment descriptor is that used in deployTest.car:

<component>

 <description>Simple CAR containing a worker, service, and image extractor and a deployment descriptor, this file, that deploys these elements in the portal to illustrate the use of the deployment descriptor features.</description>

 <worker class="com.sct.pipeline.uportal.channels.deployTest.HelloWorker"/>

 <service>
  <name>Hello Worker Hits-Count Service</name>
  <class>com.sct.pipeline.uportal.channels.deployTest.HitsService</class>
  <jndi_name>hitsService</jndi_name>
 </service>

 <ext contentHandler="com.sct.pipeline.uportal.channels.deployTest.Extractor">
  <file pathInCar="com/sct/pipeline/uportal/channels/deployTest/youveGotIt.jpg"/>
 </ext>

</component>

The worker element adds to the worker definitions found in properites/worker.properties. This element has no content and one required attribute: class. This attribute must contain the fully qualified name of a java class that implements the org.jasig.portal.IWorkerRequestProcessor interface. To prevent naming collisions, the name of the worker is the same as the class name with all periods, (.), replaced with underscores, (_). This greatly reduces the posibility of naming collisions between workers declared in different CARs.

To access such a worker, a channel must generate an appropriate URL by using the getBaseWorkerURL() methods in ChannelRuntimeData and passing in the worker's name. For deployTest.car's worker, we would aquire a URL to access that worker with the following code within our channel, assuming the rd variable contains the ChannelRuntimeData object and that the full path has been truncated for brevity.

rd.getBaseWorkerURL( "com_sct_..._HelloWorker" );

The returned value could then be passed into the channel's XSLT stylesheets to provide links to access the worker as needed.

The service element adds to the service definitions found in properties/services.xml. There is no interface defined for services; they are simply utilities that should be started when the portal is started. Optionally they may be accessed by the portal's JNDI context. The contents of the service element must conform to the allowed structures of the services elements in the properties/services.xml file.

The ext element is an extension block used to declare code that should be processed when the portal is started and may include configuration information that should be passed to that code. This element has a single required attribute: contentHandler. This attribute must contain the fully qualified name of a Java class that implements the org.xml.sax.ContentHandler interface. The contents of the ext element are opaque to the CAR deployment descriptor processing code. The content handler declared is instantiated and its startDocument method is called. Then the SAX content handler events for the contents of the ext element are passed to this object. When the contents are fully parsed, the content handler's endDocument method is called.

For example, the deployTest.car deployment descriptor's ext element contains a single element: file. It has a single attribute, pathInCar, containing the path to an image that should be moved to the web visible media directory. The content handler uses this to see if the image is already available in the web visible area and if not to extract it from the CAR and place it in that location.

Another example of using an extension block can be seen in an archive provided for integrating an external system with SCT's Luminis product. The Luminis product provides the ability to declaratively indicate how some URLs are accessed (e.g. accessed by HTTPS, by someone with the "student" role, etc.) As part of integration, this external system provides a CAR housing a worker that can receive information from the external system and take action in Luminis based on that information. The extension block declared in the CAR's deployment descriptor is illustrated below. Note the much more complex and yet perfectly acceptable XML structure contained within the ext block.

 <ext contentHandler="com.sct.pipeline.SecureURLMapHandler">
  <DEFINITION rootAt="cp">
   <NODE name="worker.com_sct_pipeline_Worker.uP">
    <LABEL name="secure"> true </LABEL>
    <LABEL name="acl"> role:admin </LABEL>
    <LABEL name="auth-method"> BASIC </LABEL>
   </NODE>
  </DEFINITION>
 </ext>

This block is used by the SecureURLMapHandler to configure the secure URL map to enforce that access to the URL "cp/worker.com_sct_pipeline_Worker.uP" is only allowed for a user with the admin role making the request via HTTPS. Additionally, since a deamon process initiates these requests, basic authentication is used so that this transaction can take place in a single HTTP request and response cycle including authentication.

How Do You Build Them?

To build a CAR first create the directory structure that is desired within the CAR. For example, lets extract, delete, and then rebuild deployTest.car. Assume that we have created a temp directory and placed in it deployTest.car. Now in a command shell change directory into temp and extract the CAR's contents.

jar -xf deployTest.car

There will now be two subdirectories in temp: META-INF and com. META-INF contains the comp.xml deployment descriptor and the standard manifest file generated by the jar utility. The com directory is the top of the package relative directories housing the java source files, compiled class files, and an image file. After deleting deployTest.car we recreate it by executing:

jar -cf deployTest.car com META-INF

We now see deployTest.car again. Don't forget to specify both META-INF and com in the above command or the CAR will be lacking some of its resources.

Although this is generally how a channel archive is created the process is usually more involved. For example, in a development environment using CVS to version and backup your code, there would be a CVS directory in each of the subdirectories. These should not be placed into the archive. A better approach would be to use a tool like ant to automate copying the files needed into a temporary directory. Files will most likely come from multiple locations: java sources from a source area, class files from a build area, images from somewhere else, etc. ant can bring them all together, execute the jar utilty, and then place the CAR wherever it is supposed to go. It takes a little time to get familiar with ant but the time savings are worth it.

What Does the Future Hold?

One feature that is still in the works is automatic publishing of channels. So stay tuned for that feature to be completed. If channel archives don't offer some other feature that you desire, then get involved with the JASIG uPortal development effort and either create that feature yourself and contribute it back or at least share your ideas in the active email list.


mrb 9/09/2003