Martin Gale’s blog

web 2.0, extended integration, pervasive messaging and other technical thoughts

Sample composite application in Lotus Expeditor 6.2

Posted by Martin on June 22, 2009

One of the key features of Lotus Expeditor and Lotus Notes 8.5 is the Composite Applications Environment (CAE). The CAE provides a framework for integration “on the glass” of desktop applications such as native applications, terminal emulation or browser-based applications that are not otherwise easily integrated. This posting shows a very simple example of how to use the tools provided in the Lotus Expeditor 6.2 Client for Desktop to integrate together a set of disparate browser-based applications by way of illustration.

Preparing your environment

There are two possible ways to configure a composite application in the CAE — via extension points in plug-ins created using the Expeditor Toolkit or via GUI tools provided for the Expeditor Desktop Client itself. This sample uses the GUI method to create the composite application.

The GUI tooling is not installed by default with the Expeditor Desktop Client, but is supplied in the install media. You will need to install the required feature from the update site supplied with the Client inside the desktop\updates\platform folder.

Creating a new Composite Application

Once the CAE feature has been installed, you can begin creating your Composite Application using the File menu.

image

Selecting New Composite Application… will launch a modal dialog box prompting you for a filename to store your Composite Application. In this case we’ve chosen to store the Composite Application under the name Radio 1 Sample.

image

Having clicked OK we then see a blank Composite Applications page as shown below.

image

The scenario we are going to create is a very simple one whereby we take two separate web applications from different vendors and integrate data between them within a simple task flow. To begin editing the page, we follow the Actions -> Edit Application menus. We see the editing screen appear.

image

Configuring containers

The CAE works on the principle that the applications are added into the page using a set of generic containers. These containers provide a common interface around the various different desktop client technologies such that they can ultimately be wired together in a standardised fashion. Since the applications we are integrating are browser based, we will use the Browser Container shown on the Component Palette. Dragging the container onto the page creates a default configuration of the container.

image

We will modify this default configuration to reflect our application. We will use data contained within the BBC Radio 1 Album Chart to pre-populate a search in Amazon for the name of the top album. I have deliberately chosen these since they are two public sites over which I could not reasonably integrate myself in any other fashion.

We will start by configuring a container for the BBC Radio 1 web site as the source of the data. To start this process we select the Edit Component Properties option from the context menu shown below.

image

A modal dialog box then appears allowing various settings about the container. We shall first change the title and description to something more meaningful.

image

Now, we will configure the details of the starting page for the container and also define the data properties that the container will expose to the environment for composition.

image

You will see that not only have we changed the initialURL property but we have created two further properties albumArtist and albumTitle. In the case of the latter two we have ticked the Wireable box. This tells the CAE that these are data properties on this container that can be integrated (or wired) with other containers.

We now need to specify how and when albumArtist and albumTitle are populated by the container. The CAE has the notion of a landmark with an associated set of events for containers. A landmark indicates a specific logical point in the application. In the case of a web browser, we can specify either a URL or the title of the page in question. For our example we are interested specifically in the Album Chart so we shall set a landmark of the Album Chart page using its URL. We select the Landmarks tab on the dialog, then select URL as the Type of Landmark and finally click the Add Landmark button.

 

image

For our landmark we can add a set of event handlers associated with it. This tells the CAE what to do when the particular landmark is reached. For a web browser, the most common event is the Content Complete event which is fired when the web page has completed loading. In response to an event we can tell the CAE to interrogate the container to set the data in one of the Wireable properties we defined earlier. In this case we will specify the XPath within the page in the browser of the HTML element containing the data elements we are interested in. A quick and simple way of finding the XPath of an HTML node is to use the Firebug utility for Firefox.

image

Now we can specify our event handler. We will add two operations for the Content Complete event on the page to copy the values contained in the HTML into the attributes we defined earlier. We click the Add Event button and then Add Operation with Content Complete set as the Event.

image

The above Operations effectively the following:

  • When the page completes loading, publish the value held at the XPath /html/body/div/div[2]/div[2]/div[4]/div/table/tbody/tr[2]/td/table/tbody[2]/tr/td[4]/h4 into the property named albumArtist.
  • When the page completes loading, publish the value held at the XPath /html/body/div/div[2]/div[2]/div[4]/div/table/tbody/tr[2]/td/table/tbody[2]/tr/td[4]/h4 into the property named albumTitle.

Note the xpath: prefix indicating to the Browser Container that the meta-data that follows is a valid XPath string. Note that this meta-data is container specific, other containers may have different schemes.

Pressing OK on the dialog box will save the changes and present the modified container in the editor.

image

Now we need to configure a container for Amazon in the same way. We will now repeat the process above, note that you can drag your new container from the palette onto the page and attach it to a convenient point on the page (e.g. the bottom edge so the screen is split horizontally). This time, we will set the intialURL property to http://www.amazon.co.uk for the Amazon home page and a description and title to that effect. We will also do the following:

  1. Add a Wireable property called searchQuery.
  2. Create a Title landmark for the page with the title “Amazon.co.uk Music: over two million music items, including chart CDs, back-catalogue, vinyl, cassettes” (omitting the speech marks). Title landmarks are useful when the URLs can vary e.g. some sites use a URL rewriting mechanism for session affinity.
  3. Add a Content Complete event with a single Operation to Receive from the searchQuery property and put the value into the XPath //*[@id="twotabsearchtextbox"].

A snapshot of the Browser Container settings for the Amazon container is shown below.

image

Notice that in this case we choose to Receive (rather than Publish) as we will have incoming data to be plugged into the Amazon page rather than data to be propagated out as we had before. We now have two containers configured on our page.

image

Wiring the containers into a composite

We now need to wire the two containers together. Note that at this point we will no longer be talking about the specifics of how the containers give and receive data (i.e. no more talk of XPaths). This is an important feature of the CAE since it means that relatively few technical skills are required to assemble a set of containers once they have been configured. At the wiring level, the user deals only in terms of properties and wires.

image

From the context menu shown above we select the Wiring menu option. We then see the Wiring view showing the two containers we have configured.

 image

Notice how for each container we configured earlier that each Wireable property is shown on the graphical representation of the container.

What we want to do is wire the albumArtist property into the searchQuery such that when the events we configured for the container fire, the data will flow along the wire from the BBC Radio 1 Charts container into the Amazon (UK) container. We do this simply by clicking and dragging the albumArtist property and releasing it over the searchQuery property. A blue arrow appears whilst the drag/drop is in progress, once the drop is complete we see the following:

image

We complete our composite application by clicking OK on the Wiring screen, then the File -> Save and Close menu options on the editor screen to save the changes to the Composite Application. When we return to the Expeditor desktop, we now see a tab with our Composite Application contained within it.

image

Testing the application

We are now ready to test the application. To show the integration working, we simply navigate the BBC page to the album chart using the link within the page to http://www.bbc.co.uk/radio1/chart/albums.shtml and then click through on Amazon to the Music Department. We should see the following screens.

image

As you can see when we click onto the Music Department, the title of the top album in the Radio 1 chart has been extracted from the HTML and populated into the input field on the Amazon page. Our composite application is complete and working.

Posted in Snippets | Tagged: , , , , , | 1 Comment »

AES 256 encryption using Java and Lotus Expeditor 6.2

Posted by Martin on March 24, 2009

In a recent engagement, one of the requirements for the application was the ability to encrypt the payload of an MQTT message using 256 bit Advanced Encryption Standard (AES) encryption, where the key was created using a combination of a user name and a pre-shared key. This was to be accomplished using the Lotus Expeditor Device JRE profile running on a desktop PC.

I’ve captured how I did this for posterity here, it’s basically the standard Java Cryptography Architecture (JCA) so should be generally useful in other domains too. I’ve used a really simple OSGi bundle that performs the cryptography in the start() method to prove the point within the Expeditor toolkit.

Creating a the encryption key

In this scenario, I needed to construct a 256 bit key derived from a user name and pre-shared key. Java provides the SecretKeySpec class to enable the provision of application specific data within a key.

// My user name
byte[] loginId = "galem".getBytes();
// A sample pre-shared key -- this might be something like an account number
// or employee number or such like.
byte[] preSharedKey = "ACME-1234ACME-1234ACME-1234".getBytes(); 
byte[] encodedKey = new byte[loginId.length + preSharedKey.length];

System.arraycopy(loginId, 0, encodedKey, 0, loginId.length);
System.arraycopy(preSharedKey, 0, encodedKey, loginId.length, preSharedKey.length);

// The SecretKeySpec provides a mechanism for application-specific generation
// of cryptography keys for consumption by the Java Crypto classes.

// Create a key specification first, based on our key input.
SecretKey aesKey = new SecretKeySpec(encodedKey, "AES");

Encrypting the data

Now we have the key, we can now create a Crypto class to encrypt the data. I’ve output the result to the console to show a “before and after” view.

// Create a Cipher for encrypting the data using the key we created.
Cipher encryptCipher = Cipher.getInstance("AES");
// Initialize the Cipher with key and parameters
encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

// Our cleartext
String clearString = "This is another example";
byte[] cleartext = clearString.getBytes();

System.out.println("Plain text: "+clearString);

// Encrypt the cleartext
byte[] ciphertext = encryptCipher.doFinal(cleartext);

System.out.println("Encrypted: "+new String(ciphertext));

Decrypting the data back again

Finally, to show the encrypted data can be returned to its original form, we’ll reverse the process.

// Now decrypt back again...
// Decryption cipher
Cipher decryptCipher = Cipher.getInstance("AES");
// Initialize PBE Cipher with key and parameters
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey);

// Decrypt the cleartext
byte[] deciphertext = decryptCipher.doFinal(ciphertext);

System.out.println("Decrypted: "+new String(deciphertext));

Full listing

package com.ibm.issw.sample.crypto;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.eclipse.core.runtime.Plugin;
import org.osgi.framework.BundleContext;

/**
 * The activator class controls the plug-in life cycle
 */
public class Activator extends Plugin {

	// The plug-in ID
	public static final String PLUGIN_ID = "com.ibm.issw.sample.crypto";

	// The shared instance
	private static Activator plugin;

	/**
	 * The constructor.
	 */
	public Activator() {
	}

	/**
	 * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext)
	 */
	public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;

		try {

		    // In this example we will just use a combination of a user name and
		    // a pre-shared key.

		    // My user name
		    byte[] loginId = "galem".getBytes();
		    // A sample pre-shared key -- this might be something like an account number
		    // or employee number or such like.
		    byte[] preSharedKey = "ACME-1234ACME-1234ACME-1234".getBytes();

		    byte[] encodedKey = new byte[loginId.length + preSharedKey.length];

		    System.arraycopy(loginId, 0, encodedKey, 0, loginId.length);
		    System.arraycopy(preSharedKey, 0, encodedKey, loginId.length, preSharedKey.length);

		    // The SecretKeySpec provides a mechanism for application-specific generation
		    // of cryptography keys for consumption by the Java Crypto classes.

		    // Create a key specification first, based on our key input.
		    SecretKey aesKey = new SecretKeySpec(encodedKey, "AES");

		    // Create a Cipher for encrypting the data using the key we created.
		    Cipher encryptCipher = Cipher.getInstance("AES");
		    // Initialize the Cipher with key and parameters
		    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

		    // Our cleartext
		    String clearString = "This is another example";
		    byte[] cleartext = clearString.getBytes();

		    System.out.println("Plain text: "+clearString);

		    // Encrypt the cleartext
		    byte[] ciphertext = encryptCipher.doFinal(cleartext);

		    System.out.println("Encrypted: "+new String(ciphertext));

		    // Now decrypt back again...
		    // Decryption cipher
		    Cipher decryptCipher = Cipher.getInstance("AES");
		    // Initialize PBE Cipher with key and parameters
		    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey);

		    // Decrypt the cleartext
		    byte[] deciphertext = decryptCipher.doFinal(ciphertext);

		    System.out.println("Decrypted: "+new String(deciphertext));

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext context) throws Exception {
		plugin = null;
		super.stop(context);
	}

	/**
	 * @return the shared instance
	 */
	public static Activator getDefault() {
		return plugin;
	}

}

Posted in Snippets | Tagged: , , , , , , | Leave a Comment »

Tag cloud from Wordle

Posted by Martin on March 4, 2009

Over a cup of tea this morning I revisited wordle.net which an online resource for making tag clouds. One thing I noticed is you can supply a feed URL so in the interests of science, I pointed it at my current blog homepage. I was interested to compare the tag cloud created by tags with that generated from the free text itself.

You can find the full version on the wordle.net page, here’s the thumbnail:
Wordle: Martin Gale's Blog

Posted in Uncategorized | Tagged: , , | Leave a Comment »

Persisting custom attributes for an iWidget in IBM Mashup Center

Posted by Martin on March 3, 2009

In an earlier entry, I talked through a simple example of how to construct an iWidget for IBM Mashup Center. This afternoon I presented at the WebSphere User Group meeting at IBM Bedfont and was asked about the persistence of state in iWidgets, saving and loading and so on and so it seemed a sensible next step to augment the earlier sample to show how this is done.

ItemSets and attributes

As well as local instance variables inside the Dojo class, the iContext instance provides us with hooks into a standardised structure of instance-specific attributes. These attributes can be defined and set declaratively using the XML descriptor for the iWidget. To extend the earlier example, first modify the XML to contain the following element structure inside the <iw:iwidget> tag:

<iw:itemSet id="attributes" private="false" onItemSetChanged="itemSetChanged">
   <iw:item id="pollInterval" value="5" readOnly="false"/>
</iw:itemSet>

This will make the pollInterval property we coded earlier as an instance variable into a managed attribute inside an ItemSet. The iWidget specification defines the ItemSet data type (and ManagedItemSet sub-type) to describe a generic key-value pair data store for iWidgets. The iContext exposes to us the attributes as an ItemSet instance, with accompanying accessors to get, set and save changes to the values contained within it.

The next step, therefore, is to wire this logic into our existing Dojo class to exploit this feature. At the top of the onLoad method, add the following line:

this.pollInterval = this.iContext.getiWidgetAttributes().getItemValue("pollInterval");

This reads the currently saved (or indeed default) value for the given attribute, and populates our instance variable with the value.

Similarly, our routine to save changes to the iWidget now needs to update the attributes and save the changes so they can be persisted with this instance on the page. Add the following lines after the call to set the pollInterval instance variable in the saveParams method:

this.iContext.getiWidgetAttributes().setItemValue("pollInterval", this.pollInterval);
this.iContext.getiWidgetAttributes().save();

What you will now discover is when you modify the setting and save the page, when the page is reloaded, you will see the attribute load with the modified value.

Posted in Uncategorized | Tagged: , , , , , , , | Leave a Comment »

Developing iWidgets for IBM Mashup Center – a quick-start guide

Posted by Martin on January 30, 2009

I’m a great believer in a simple worked example so rather along the lines of my earlier jump-start for SSL and MQTT, I thought I would compose a similar posting for developing iWidgets to use in the IBM Mashup Center. For a deeper dive on iWidgets, have a look at the iWidget Programming Guide, Specification and also the IBM Mashup Center wiki, all of which are there to support the developer. There are several different examples floating around that show various different aspects of iWidgets, I’m going to show a really simple example that reads an ATOM feed and displays the result.

In simple terms, a widget is a user interface component. With particular regard to mashups, the widget is mechanism by which data feeds are visualised. In IBM Mashup Center, when you choose to add a feed to your workbench, you can specify a particular widget to handle the visualisation. iWidget is an emerging standard for defining these user interface components. This is to promote highly reusable and interoperable components that behave in a common way and expose their services in a consistent fashion. This enables the rapid assembly of business applications from components produced by different developers and providers. IBM Mashup Center’s implementation is based on the Dojo Toolkit and the iWidgets come as a package of some JavaScript code and configuration files all wrapped up in a WAR file for deployment.

Thanks to Matt for his help along the way. For those using RSA or RAD 7.x, you may want to check out his guide to creating a development environment.

Creating the iWidget

The iWidget itself is a combination of a Dojo widget class and an XML definition file.

The iWidget XML definition

The XML definition describes the widget configuration with details such as:

  • The modes the iWidget supports. Rather like portlets, iWidgets can have a View mode, an Edit mode and a Help mode, the former being the primary presentation logic, the latter two being the interfaces for the user to modify or gain help about the widget respectively.
  • References to any required resources such as JavaScript files. You should have at least one referenced resource which is the .JS file containing the Dojo widget definition.
  • Markup content for each of the required modes. This is the basic display logic for the iWidget and can of course contain other Dojo widgets within it.
  • The name of the Dojo class implementing the programmatic logic for the widget. This is referred to as the iScope in the XML definition.

My sample iWidget definition is as follows:

<iw:iwidget id="samplewidget"    xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget"    iScope="lm.samplewidget" allowInstanceContent="true"    supportedModes="view edit" mode="view" lang="en"> 

    <iw:resource uri="samplewidget.js" /> 

    <iw:content mode="view">
    <![CDATA[       
         <div id="container">
            Value: <span id="valueNode">0</span>
         </div>           
     ]]>   
    </iw:content>   
    <iw:content mode="edit">       
    <![CDATA[       
       <div style="background-color: white; padding: 2px; border: solid #4078C2 1px;">
            <table border="0">
                <tr>
                    <td colspan="2"><h2>Data feed</h2></td>
                </tr>
                <tr>                   
                    <td>Poll interval (secs):</td><td><input id="pollInterval" size="5"></td>
                </tr>
                <tr>
                    <td colspan="2" height="5"></td>
                </tr>
            </table>           
            <span style="margin-right:12px"><a href="javascript:iContext.iScope().cancel();" id="_IWID_CONF_CANCEL" class="lotusAction" title="Cancel settings">Cancel</a></span>
            <input id="_IWID_CONF_SAVE" title="Save settings" class="lotusFormButton" style="margin-right:10px;" type="button" value="Save" name="save" onclick="iContext.iScope().saveParams();return false;" />
        </div>   
     ]]>
    </iw:content>
</iw:iwidget>

As you can see, the iWidget supports both View and Edit modes. The View simply shows a value, the Edit mode allows us to modify how often the underlying data feed is polled for updates. You will notice from the JavaScript URL link in the Edit markup how we invoke methods on the underlying Dojo widget instance for the iWidget — iContext.iScope()returns the object reference. I am reusing some of the Mashup Center CSS styles for the Save and Cancel triggers so the widget fits into the overall look and feel.

The iWidget Dojo class

In MVC terms you can regard the Dojo class as a controller for the iWidget — for example orchestrating how it responds to events, reads a feed (i.e. the model), manipulates the UI (i.e. the view) and so on. The class implements lifecycle methods for the iWidget such as:

  • onLoad() triggered when the iWidget has finished loading.
  • onview() triggered when the View mode has rendered.
  • onedit() triggered when the Edit mode has rendered.

iWidgets also have a number of name/value pair attributes for storing configuration and data about the widget. These can be anything of your choosing but the one to remember is feedURL since this is set by the Mashup Center when you choose to use your widget to visualise a particular feed. The following snippet is my onLoad() handler and you will see how we extract the content of feedURL and store it as an instance variable.

onLoad: function(){
    this.feedURL = this.iContext.getiWidgetAttributes().getItemValue("feedURL");
    console.info("feedURL = "+this.feedURL);
    this.domID = "_" + this.iContext.widgetId + "_";
    dojo.subscribe ("events/"+this.domID+"/poll", this, "pollForData");
}

There are a few points to note. Firstly, because this is essentially a Dojo widget, we are free to use all the various capabilities of the Dojo Toolkit such as the publish/subscribe framework which I am using here for triggering polls of the ATOM feed. Secondly, you can see how we can interrogate the surroundings of the widget using the iContext instance variable, in this case to generate ourselves a unique identifier to namespace the pub/sub topic and to gain access to the attributes for the instance.

The onview method in my widget is very simple since all I am using it for is as a signal to start polling.

onview: function() {
    if (this.feedURL) {
        console.info("Feed URL is "+this.feedURL);
        this.pollForData();
    }
}

The pollForData() method uses standard Dojo xhrGet to retrieve the ATOM feed. Once processing is complete, the routine simply sets a timeout to poll again.

    pollForData: function() {
        // Request data from the given ATOM feed
        var feedURL = this.feedURL;
        var context = this;
        console.info("Polling for data from "+this.feedURL);
        var bindArgs = {
            url: feedURL,
            error: function(error, ioArg)
                {
                    window.alert("Problem loading feed: "+error);
                }
            ,
            load: dojo.hitch(context, function(xmldata, ioArg){
                if (xmldata != null ) {
                    var xmlDoc = null;
                    if ((xmlDoc = dojox.data.dom.createDocument(xmldata)) === null) {
                        window.alert("No data returned");
                        return;
                    }
                    // Simply reads the text in the content field of an ATOM entry
                    var itemNodes = xmlDoc.getElementsByTagName("content");
                    for (var i = 0; i < itemNodes.length; i++) {
                        var childNodes = itemNodes[i].childNodes;
                        for (var j=0;j<childNodes.length;j++) {
                            // Should only be one <content> node in the ATOM
                            // but if there are more we will take the last one.
                            var val = childNodes[j].nodeValue;
                            this.iContext.getElementById("valueNode").innerHTML = val;
                        }
                    }
                } else {
                    window.alert("No data returned by feed URL.");
                }
                // Now poll again
                this.pollTimeout = window.setTimeout("dojo.publish('events/"+this.domID+"/poll',[]);", this.pollInterval*1000);
            })
        };
        dojo.xhrGet(bindArgs);
    }

onedit populates the form field on the Edit view so that the correct value is presented to the user. In order to activate changes (or indeed undo changes and return to the view) we also need to add two additional methods of our own.

    cancel: function() {
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    saveParams: function () {
        this.pollInterval = this.iContext.getElementById("pollInterval").value;
        // Restart any polling with the new interval
        if (this.feedURL) {
            if (this.pollTimeout) {
                window.clearTimeout(this.pollTimeout);
            }
        }
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    onedit: function(){
        this.iContext.getElementById("pollInterval").value = this.pollInterval;
    }

These methods demonstrate a few key features. The first is how to switch modes from within the widget — in both the cancel() and saveParams() methods we wanted return to the View mode so we fired an event into the surrounding context which the Mashup Center will pick up and cause the widget to switch back from Edit. The second point of note is that the iContext provides a getElementById() method of its own — i.e. return the named DOM element within this specific widget’s context rather than the whole HTML page (which may contain other instances of the same widget).

Here is the full listing for the iWidget class.

dojo.provide("lm.samplewidget");
dojo.declare("lm.samplewidget", [], {
    domID: null,
    attributes: ["feedURL"],
    feedURL: null,
    pollInterval: "5",
    pollTimeout: null,

    onLoad: function(){
        this.feedURL = this.iContext.getiWidgetAttributes().getItemValue("feedURL");
        console.info("feedURL = "+this.feedURL);
        this.domID = "_" + this.iContext.widgetId + "_";
        dojo.subscribe ("events/"+this.domID+"/poll", this, "pollForData");
    },

    onview: function() {
        if (this.feedURL) {
            console.info("Feed URL is "+this.feedURL);
            this.pollForData();
        }
    },

    pollForData: function() {
        // Request data from the given ATOM feed
        var feedURL = this.feedURL;
        var context = this;
        console.info("Polling for data from "+this.feedURL);
        var bindArgs = {
            url: feedURL,
            error: function(error, ioArg)
                {
                    window.alert("Problem loading feed: "+error);
                },           
            load: dojo.hitch(context, function(xmldata, ioArg){
                if (xmldata != null ) {
                    var xmlDoc = null;
                    if ((xmlDoc = dojox.data.dom.createDocument(xmldata)) === null) {
                        window.alert("No data returned");
                        return;
                    }
                    // Simply reads the text in the content field of an ATOM entry
                    var itemNodes = xmlDoc.getElementsByTagName("content");
                    for (var i = 0; i < itemNodes.length; i++) {
                        var childNodes = itemNodes[i].childNodes;
                        for (var j=0;j<childNodes.length;j++) {
                            // Should only be one <content> node in the ATOM
                            // but if there are more we will take the last one.
                            var val = childNodes[j].nodeValue;
                            this.iContext.getElementById("valueNode").innerHTML = val;
                        }
                    }
                } else {
                    window.alert("No data returned by feed URL.");
                }
                // Now poll again
                this.pollTimeout = window.setTimeout("dojo.publish('events/"+this.domID+"/poll',[]);", this.pollInterval*1000);
            })
        };
        dojo.xhrGet(bindArgs);
    },
    cancel: function() {
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    saveParams: function () {
        this.pollInterval = this.iContext.getElementById("pollInterval").value;
        // Restart any polling with the new interval
        if (this.feedURL) {
            if (this.pollTimeout) {
                window.clearTimeout(this.pollTimeout);
            }
        }
        this.iContext.iEvents.fireEvent("onModeChanged",null,{newMode:'view'});
        return;
    },
    onedit: function(){
        this.iContext.getElementById("pollInterval").value = this.pollInterval;
    }
 });

Here is a sample of the ATOM feed that is consumed by the sample.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Example pressure reading Feed</title>
  <link href="http://example.org/"/>
  <updated>2003-12-13T18:30:02Z</updated>
  <author>
    <name>Martin</name>
  </author>
  <id>urn:uuid:62a76c80-d399-11d9-b93C-0003939e0af6</id>
  <entry>
    <title>Pipeline pressure reading</title>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
    <updated>2009-01-30T18:30:02Z</updated>
    <summary>Pressure reading (in PSI)</summary>
    <content>23</content>
  </entry>
</feed>

Packaging and deploying the iWidget

I have assembled my iWidget in a WAR file structure with the XML definition and JavaScript in the root.

image

Two further files are required in the WEB-INF folder, mashup.properties and catalog.xml. The former contains lower-level information about the widget such as a unique identifier and the name of the context root, the latter contains more descriptive information about the iWidget.

Here is my mashup.properties file.

contextRoot=SampleWidget
componentName=samplewidget

And here is the catalog.xml file.

<catalog>
    <category name="Demo">
          <title>
          <nls-string lang="en">Demo</nls-string>
          </title>
          <description>
          <nls-string lang="en">Demo Widgets</nls-string>
          </description>
        <entry id="SampleWidget" unique-name="SampleWidget">
            <title>
                <nls-string lang="en">Sample Widget</nls-string>
            </title>
            <description>
               <nls-string lang="en">Shows a value read from an ATOM feed.</nls-string>
            </description>
            <definition>SampleWidget.xml</definition>
            <content>http://www.ibm.com</content>
            <preview>http://www.ibm.com</preview>
            <icon>images/icon.png</icon>
        </entry>
    </category>   
</catalog>

With all these files in place, we simply export the structure (I am using RAD 7.0) to a WAR file and we can now install it into the IBM Mashup Hub for people to use. One thing to note, if you specify an icon in the XML, ensure this is exported as part of your WAR otherwise the WAR will not install correctly into the Mashup Hub.

Using the iWidget

The final step is to try out the iWidget. We shall do this by adding a feed to the Mashup Hub and then in turn to Lotus Mashups specifying our widget as the viewer. As I’ve done previously, I’ve made a short video to show how it is done.

Posted in Snippets | Tagged: , , , , , , , , | 8 Comments »