Channel ARchives
uPortal Channel ARchives
Contents
What They Are
Installation
Do They Require Different Code?
Building Them
Deployment Descriptors and Their Supported Tags
<worker> |
|
<service> |
|
<ext> |
|
<channel-definition> |
|
<channel-type> |
|
<database> |
|
<processIf> |
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, |
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 xsrc="{$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" xhref="stylesheets/channel.xsl"> <?xml-stylesheet title="CarListStyleSheet" xhref="../carlot/stylesheets/channel.xsl"> <?xml-stylesheet title="CarListStyleSheet" xhref="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 xhref="../tree/view.xsl" /> <?xsl:include xhref="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.
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 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.
As of version 2.2 deployment descriptors supported the ext, service, and worker tags. As of 2.4 deployment descriptors have been extended with the channel-definition, channel-type, database, and processIf tags. Therefor, the deployment descriptor currently has the following general form for its document type. For the structure of the channel-definition tag see the channelDefinition.dtd file in the portal's webpages/dtd directory.)>
<!ELEMENT component (description | ext | service | worker | processIf | channel-type | database | channel-definition)+> <!ELEMENT description (#PCDATA)> <!ELEMENT ext (#PCDATA)> <!ATTLIST ext contentHandler CDATA #REQUIRED> <!ELEMENT service (#PCDATA)> <!ELEMENT worker EMPTY> <!ATTLIST worker class CDATA #REQUIRED> <!ELEMENT channel-type (name, class, description, uri)> <!ELEMENT name (#PCDATA)> <!ELEMENT class (#PCDATA)> <!ELEMENT uri (#PCDATA)> <!ELEMENT database (drop-tables, create-tables, populate-tables, create-script, tables, data?)> <!ELEMENT drop-tables (#PCDATA)> <!ELEMENT create-tables (#PCDATA)> <!ELEMENT populate-tables (#PCDATA)> <!ELEMENT create-script (#PCDATA)> <!ELEMENT tables (#PCDATA)> <!ELEMENT data (#PCDATA)> <!ELEMENT processIf (service | ext | database | channel-type | channel-definition)+> <!ATTLIST processIf fname CDATA #REQUIRED version (equalTo | greaterThan | greaterThanOrEqual | lessThan | lessThanOrEqual | notEqualTo) #REQUIRED major CDATA #REQUIRED minor CDATA #REQUIRED micro CDATA #REQUIRED setMajor CDATA #REQUIRED setMinor CDATA #REQUIRED setMicro CDATA #REQUIRED setDescription CDATA #REQUIRED>
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>
Plugging In and Using a Worker
The worker element adds worker definitions into the portal beyond those 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. Since workers can specify the content type being sent back they represent one way of serving up content of any type from the portal.
Installing a Service
The service element adds services into the portal beyond those of 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 structure of this file is defined by the services.dtd located in the portal's webpages/dtd directory.
For example, the deployTest.car deployment descriptor's service element causes a simple counter class to be instantiated and accessible via the portal's JNDI context. Since a new instance of the worker class is instantiated for each request destined for a worker this long lived counter class is used to keep track of the number of times this worker was accessed.
Using Extension Blocks
The ext element is an extension mechanism used to perform some functionality not available through the supported deployment descriptor tags. This block can be used to prototype functionality that can later be promoted to new descriptor tags. This block may include configuration information that should be passed to the code accessed by the processing of this block. 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.
Publishing Channels
As of version 2.4 a channel archive can automatically publish channels that it contains. However, since the categories and groups at one institution most likely will not match those of another institution, groups and categories specified in the channel-definition tag are ignored. Channels that are meant to show up in the list of channels that users can subscribed to are placed in a top level Auto-Published category that is created automatically if it doesn't exist. For channels that are not meant to be subscribed to by users but rather appear in the portal in unique locations and ways much like the ChannelManager channel, the channel definition can include the optional empty makeFNameAccessibleOnly element. This causes that channel to be published in such a way that they don't show up in the list of channels available for subscription. Such a channel is accessible in focus mode only by hitting a portal URL with a query parameter of uP_fname=... with the value being the functional name of the channel as defined by the fname element.
If a channel already exists in the portal having the same functional name as that specified for the channel being published the channel will not be published. Therefore, care must be taken to have unique functional names for channels. Additionally, the processIf tag can be used to only cause a channel to be published once and not attempted every time the portal starts up and deployment descriptors are processed.
During automatic publishing of these channels access to the channels is granted only to administrators. This allows administrators to review the channels before granting access to other constituents of the portal community. Channels targeted for subscription can then be granted to other users via the Channel Manager channel. Since channels published with the makeFNameAccessibleOnly element don't appear in the Channel Manager channel access must be granted by assigning the subscribe permission for that channel to groups using the Permissions Manager channel.
To see auto-publishing in action you can use either of the two links below. The first downloads the Classifieds channel created by Pete Boysen of Iowa State University packaged up as a CAR. This CAR was created using the information and suggestions presented in this document. By dropping it into WEB-INF/cars and bouncing the portal the channel will be published automatically. This CAR also contains a database element as outlined below and automatically installs the tables that classifieds needs to function. Both of these elements are wrapped in a processIf tag as outlined below to ensure that they are processed only once. This CAR was built using JDK 1.4.2. After restarting the portal, log in as an administrator so that you can see the Auto-Published category and subscribe to the channel. For your convenience the channel is also published with specially named but unused channel parameters whose values are descriptive text outlining what real parameters can be specified to configure the channel following Pete's excellent installation instructions.
The second CAR contains a developer tool channel that exposes the stylesheets being held in the portal's XSLT classe's internal caches. Links are provided for each stylesheet and stylesheet list allowing you to selectively purge them from the cache. This channel also auto-publishes and is ready for administrators to subscribe after the web server is bounced. If dropped into a Luminis system it will also automatically register itself in the list of links that show up in when Channel Admin is selected but only for users who have been granted access to subscribe to the channel which by default is administrators. This CAR was built using JDK 1.3 and so should work on that JDK or later versions.
Adding Channel Types
As of version 2.4 a channel archive can automatically add additional channel types into the portal. This is done with the channel-type element. It takes four required parameters. The class element should contain the fully qualified class name of the class that implements the channel. This should match the value specified in the channel publishing document that is accessed via the uri element. The name element contains the name of the type as it should appear in the list of types. The description element contains the description as it should appear in the the list of types.
The uri element contains the location of the channel publishing document for the channel type. This document conforms to the channelPublishingDocument.dtd file located in the webpages/dtd directory and provides dynamically generated publishing workflow steps requesting parameters unique to the channel type being published. The uri can be a fully qualified URL to the document in some remote location. Alternatively, it can be loaded from the classpath by specifying a fully qualified package path prefixed with a '/'. Finally, it can be loaded directly from within a CAR by specifying a prefix character on the full JAR resouce path as seen for the resource when performing 'jar tf someCar.car'. For example:
Load from remote location: http://someHost/some/path/to/file.cpd
Load from non-CAR, class path location: /some/path/to/file.cpd
Load from within a CAR: some/path/to/file.cpd
Installing Database Tables
As of version 2.4 a channel archive can automatically install database tables and their default data set if needed by the channel. The drop-tables, create-tables, populate-tables, and create-script elements must have a value of true or false. The tables element must contain the path to a classpath or CAR accessible XML file containing the definition of all tables needed by the channel. The structure of this document is outlined in the tables.dtd located in the webpages/dtd directory and is the form used by the DbLoader utility. If included the data element contains a similar path but to a file containing the default set of data that should be loaded into the tables. This document should be the same format used by DbLoader. In fact the processing of the database tag delegates to DbLoader to perform its functions.
The drop-tables element if set to true causes the tables to be dropped. The create-tables element if set to true causes the tables specified to then be created. The populate-tables element if set to true causes the specified default data to be loaded into the tables. The create-script element if set to true causes the SQL sent to the database to be captured and written to the log. And example of the database tag set is that contained in the classifieds.car deployment descriptor. It uses paths that cause DbLoader to access the tables.xml and data.xml files right out of the CAR without having to extract them into a directory somewhere.
<database> <drop-tables>true</drop-tables> <create-tables>true</create-tables> <populate-tables>true</populate-tables> <create-script>true</create-script> <tables>edu/iastate/ait/channels/classifieds/tables.xml</tables> <data>edu/iastate/ait/channels/classifieds/data.xml</data> </database>
Conditional Processing of Tags
As of 2.4 all other tags provided in a deployment descriptor can be wrapped in a processIf element. The purpose of this element is to prevent nested elements from being processed more than once for a given condition. The condition that can be specified is based on the version of installed software by functional name. The version is represented by major, minor, and micro components. If processed, the block will then set the version recorded by the indicated functional name to the version represented by the setMajor, setMinor, and setMicro attributes. As an example the classifieds.car contains the following element to load the database tables needed by the channel and then publish the channel. In doing so it also sets the version to 1.1.0 thereby prevent the processing from taking place again when the same descriptor is processed the next time that the portal is started.
<processIf fname="edu.iastate.ait.channels.classifieds.CClassifieds" version="lessThan" setMajor="1" setMinor="1" setMicro="0" setMajor="1" setMinor="1" setMicro="0" major="1" minor="1" micro="0"> ...
There is no restriction on the number of tags in the deployment descriptor. The classifieds.car descriptor contains only one. If a new version of classifieds required modifications to the database, an additional block could accomodate making that change possibly through the combined use of a new database element set and an extension block to perform migration. There are many possibilities.
mrb 9/23/2004