Editor's note: Know a lot about this topic? Want to share your expertise? Participate in the IBM Lotus software wiki program today.
| Lotus Quickr wiki | Lotus Sametime Unyte Meeting wiki |
|---|
To illustrate the concepts, a real, deployable sample has been developed that enables Lotus Quickr places to integrate with IBM Lotus Sametime® Unyte® to provide instant online meeting capability. In addition to Lotus Quickr developers, this article can potentially benefit those looking to develop with the Lotus Sametime Unyte API as well. The main focus of this article, however, is to explain the component development steps for Lotus Quickr. If you are looking to add instant meeting capability to your Lotus Quickr place, you can deploy the sample using the instructions provided in this article and use it as is.
This article is intended for application developers with knowledge of Java™, XML, and portlet development. Some knowledge of REST-based APIs would be useful to understand the sample code used in this article.
Lotus Quickr is collaborative software used by teams of people to share content.
Lotus Quickr can be described by three fundamental terms: content store, team collaboration, and connectors.
- Lotus Quickr is a content repository where users and teams store personal and team content.
- It allows team collaboration that lets users store, organize, access, and share content and team projects.
- It provides the user interface to Lotus Quickr content.
One of the tools designed for collaboration is Lotus Sametime Unyte Meeting.
Lotus Sametime Unyte provides integrated Web, voice, and video conferencing services to target customers ranging from individuals, small businesses and departments, to big international enterprises. Lotus Sametime Unyte offers application programming interfaces (APIs) for vendors seeking quick and easy integration of Lotus Sametime Unyte with other existing products and services and other IBM products, such as IBM WebSphere Portal and Lotus Quickr. The URI-based API allows companies to plug Lotus Sametime Unyte into their application.
This article describes how to integrate Lotus Sametime Unyte Meeting with Lotus Quickr 8.1.1 places. A sample of the Lotus Sametime Unyte Meeting Java Platform, Enterprise Edition Lotus Quickr component that supports integration has been described and analyzed in the following sections of this article. First, though, we cover a basic introduction to building components and their role in Lotus Quickr.
In this section, we describe the steps that you take to build a typical component that can be deployed to Lotus Quickr. As the name suggests, Lotus Quickr services for WebSphere Portal is built on Websphere Portal. Particularly, it uses the composite application infrastructure (CAI) offered by WebSphere Portal.
CAI defines interfaces that can be implemented to develop new components that augment the capabilities of Lotus Quickr. It is beyond the scope of this article to go into detail about the entire framework. (See related materials in the Resources section of this article for more detail on the framework.) We focus on the interfaces needed to build a component. You can also refer to the documentation for these interfaces located here:
<Quickr Install Location>/PortalServer/doc/Javadoc/spi_docs
Lotus Quickr components are used inside Lotus Quickr places. The component can be as simple as a portlet. A fairly complex component could use its own library to store contents, define custom roles that are used to tailor its functionality, and participate in backup and restore operations.
Follow these steps to build a component:
- Develop the portlet that provides the component’s user interface and other features.
- Develop the component handler by implementing the appropriate CAI interfaces.
- Provide a plugin.xml file that specifies the component handler (typically at the root of the JAR file containing the component implementation, which is packaged in the WAR file).
- Bind the portlet and the component handler using portlet.xml.
It should be noted that the component handler can be packaged either inside the portlet WAR file or in a separate JAR file that is made available in classpath. On server startup, CAI attempts to build a registry of all deployed components. It does so by loading all the plugin.xml files that are visible in classpath. The plugin.xml file for the component should provide the extension point for CAI business component, namely com_ibm_portal_app.BusinessComponents
Listing 1 provides an example.
Listing 1. Sample component plug-in declaration
<?xml version="1.0" encoding="UTF-8"?>
<plugin id="sample.plugin" version="1.0.0">
<extension
point="com_ibm_portal_app.BusinessComponents"
id="MyFirstComponent">
<provider class="sample.MyFirstHandler">
</provider>
</extension>
</plugin>
|
NOTE: Make sure that the version number includes three parts.
The provider class specified in the plugin.xml file is where you must implement all the necessary interfaces to participate as a component in the place. CAI defines a set of interfaces for a component to participate in; they are as follows:
- Lifecycle. This interface defines the necessary callbacks that the component can implement so that it can listen to events such as creation and deletion of the component instance in a place. CAI attempts to call the appropriate method in this interface when these events occur.
- Membership. The membership interface defines callback methods to listen to events associated with access control such as adding or removing access to the component for one or more users. The component can use this interface to perform additional operations when such events occur.
- Serializable. Components implement this interface to participate in backup and restore operations for places. When a Lotus Quickr place is backed up, each component is notified to back up its own content. If the component does not manage its own content, this interface need not be implemented.
- Templatable.Any Lotus Quickr place can be saved as a template. This facility allows you to quickly create a new place with the same set of components. When a place is saved as a template, CAI notifies each component in the place using this interface. By implementing this interface, components can save their state in the template so that they can retrieve it when a new instance is created from the template.
Not all these interfaces are mandatory. As mentioned earlier, you can build a simple portlet to provide the functionality and use it inside a Lotus Quickr place. Providing the implementation for one or more of these interfaces is what differentiates a portlet from a component. You should weigh the need for implementing these interfaces depending on the requirement.
Finally, after building the portlet, component handler, and plugin.xml file, you should update the portlet.xml file to bind the portlet to the component. By doing this step, you allow CAI to treat this portlet as a business component and start calling the appropriate methods on the component handler. All you need to do to make this binding is add the preference shown in listing 2 in the portlet.xml file.
Listing 2. Portlet preference linking a component handler to the portlet
<portlet-preferences>
<preference>
<name>com.ibm.portal.bc.ref</name>
<value>portal:extreg/sample.plugin.MyFirstComponent</value>
</preference>
</portlet-preferences>
|
The value for this preference has two parts. First, it is prefixed with portal:extreg/. This is the JNDI prefix for all business component references. The second part is actually the combination of plugin ID (sample.plugin) and extension point ID (MyFirstComponent) provided in our plugin.xml file.
Sample: Sametime Unyte Meeting component
This sample details how to build a custom component for use in Lotus Quickr places. The sample that is used to illustrate this component is a Lotus Sametime Unyte meetings portlet, which allows members of a place to initiate Lotus Sametime Unyte meetings using their personal Lotus Sametime Unyte account and then allows other team members to join the meeting. The component uses a portlet as a front end for a CAI (Composite Application Infrastructure, the component runtime for places) business component. This business component accesses the Lotus Sametime Unyte service using the Lotus Sametime Unyte Meeting Server API.
The sample portlet that is built allows members of a place to access Lotus Sametime Unyte meetings. A meeting can be initiated by place members by entering their personal Lotus Sametime Unyte account details in a simple form as shown in figure 1.
Figure 1. Portlet view when meeting is not in session
Clicking the Host Meeting button opens the Lotus Sametime Unyte Web Conference UI in presenter mode in a new browser window. Now, when other members of the place navigate to the portlet, they see a view that displays the details of the current meeting session, and a button that allows them to join the conference as shown in figure 2.
Figure 2. Portlet view when meeting is in session
After a meeting has completed, a view is displayed that shows the details of the previous meeting and the form used to initiate a new meeting session. Finally, the portlet supports an Edit Settings mode that presents a simple form allowing users to change the meeting service configuration used by the component.
Figure 3 shows how the various components of this sample are structured and interact.
Figure 3. Component interaction
How these constituent parts are assembled and interact is detailed in subsequent sections, beginning with the Lotus Sametime Unyte Meeting server and moving on to the business component before finishing with a discussion of the meetings portlet that acts as a view for the business component.
A prebuilt copy of the component can be found with the sample code associated with this article. In the Deployment folder, locate the unyte.meetings.war file. This file should be copied to the installableApps folder on your Lotus Quickr deployment:
<quickr>/PortalServer/installableApps
The sample is deployed using a supplied xmlaccess script. Xmlaccess is a scripting interface that provides access to configuration commands for WebSphere PortalServer. To assign a unique name to the portlet (required to include the component in the Lotus Quickr component palette) you need to deploy the component using xmlaccess. (If the component is not to be included in the palette, then the Lotus Quickr Web module deployment UI in the Administration section can be used.) The xmlaccess script is also located in the Deployment folder of the code available in the Download section of the article. The script is relatively simple; it deploys the Web module and then assigns a unique name to the portlet. For further information on xmlaccess, see the Information center. A comprehensive set of scripting samples can also be found in the <quickr>/PortalServer/doc/xml-samples folder.
To run the script, first you must copy it onto the server. Then, change to the <quickr>/PortalServer/bin folder. The xmlaccess script (xmlaccess.bat / xmlaccess.sh) can then be run, supplying parameters that specify the administrator user and password and pointing to the deployment script:
xmlaccess – url <configurl> –user <adminuser> -password <adminpwd> -in /temp/deployUnyteSample.xml
Where adminuser is the admin user ID, adminpwd is the corresponding password, the in parameter points to the deployment script, and the url parameter indicates the Portal config URL (typically http://<host>:10038/lotus/config).
After deploying the component, if you want to proceed directly to trying it out, you can skip forward to the section on adding the component to the palette below. Note that this step is optional; if you do not include the component in the palette, it can still be added to places by dropping the portlet (Sametime Unyte Meeting) into the place.
After deploying the component, a server restart is required because the component includes a server extension. These extensions are loaded into the extension registry at server startup.
Lotus Sametime Unyte Meeting Server API
Lotus Sametime Unyte offers a Web conference management API for use by partners and customers for integrating Web conferencing facilities into their applications. The API is HTTP-based and operates typically over a connection secured using SSL. The API calls are organized by passing an API request in the form of XML data sent over HTTP(S) to an advertised single entry point URL. Both GET and POST methods can be used, with the same functionality available for each (with a slightly different request format). This example uses the GET method for dispatching requests, but this example could be easily changed to use POST instead. For simplicity, this sample does not use SSL; for use in production systems, though, SSL would likely be a requirement.
The Lotus Sametime Unyte Meeting Server API offers access to the services listed in table 1.
Table 1. Services offered by the Lotus Sametime Unyte Meeting Server API
| Service | Function |
|---|---|
| Session management | Starting meetings, both in offline and presentation mode; joining meetings; removing participants from meetings; getting the details of a current or historical meeting session; changing the attributes of an in-session meeting |
| Provision management | Management (creation, deletion, and update) of service provision artifacts such as customers, subscribers, and port groups |
| Content management | Publishing and deleting presentations on the server; starting a presentation in an in-session meeting; stopping a current presentation; retrieving presentations from the server; retrieval of session recordings |
| Session management callbacks | A set of callbacks that can be used to plug application-specific processing into service events, such as conference session commencement and completion, and participant join and leave events |
Calls to the API take the form of an HTTP request to an established service entry-point URL, including details of the request as XML data. For this sample, we use the GET form of the request; the URL pattern for this is:
http://<service_entry_point>/register/api/main.asp?xml=<xmlstring>
where <xmlstring> consists of the details of the request as XML. The form for the XML depends on the particular API to be used. To use the API to start a meeting session, the XML shown in listing 3 is required.
Listing 3. Format of the Start Meeting request XML
<WDAPI ver="1.1" type="call" name="startSession" utc="utc_milliseconds"> <PARAMETERS> <SUBSCRIBER subscriber_parameters_list /> <VAPI vapi_parameters_list /> <SESSION session_parameters_list /> <PARTICIPANT participant_parameters_list /> <BRAND brand_parameters_list> brand_override_elements_list </BRAND> </PARAMETERS> <CHECK check_parameter_list /> </WDAPI> |
where name specifies the API method being requested and utc_milliseconds is the UTC time in milliseconds (in Java, this is equivalent to the call System.currentTimeMillis()). There are two main sections to the rest of the XML. The PARAMETERS segment contains all the details of the request. Not all of these details are used in this sample, such as the VAPI section, which contains voice conferencing details, and the SESSION section, which allows some additional options to be provided for the conference session. For this sample, the important pieces are the SUBSCRIBER section, which contains service details and establishes the identity of the meeting initiator, and the PARTICIPANT section, which defines how the initiating person's details display in the Lotus Sametime Unyte Meetings Web UI for the other participants.
Java wrapper around the HTTP-based Meeting Server API
Because the component is a Java-based component, you create a Java wrapper around the HTTP-based Meeting Server API for use by your component. This wrapper hides implementation details such as building the API request XML and generating the security hashes that are used in the authentication mechanism (see the section below) from the rest of the component. As part of this wrapper, you also create classes to represent each of the data sections of the API request (subscriber, brand, and so on) for convenience for client code that needs to dispatch the Meeting Server API requests.
These wrapper classes are located in the com.ibm.quickr.meetings.unyte.service package. First, look at the SametimeUnyteService class, which contains the method getStartSessionURL(...). This method assembles the request XML for the Meeting Server API call to start a new Web conferencing session in Lotus Sametime Unyte. This method results in a new browser window being opened with the Lotus Sametime Unyte conferencing UI in presenter mode. Note how a set of wrapper objects is used to pass parameters into the getStartSessionUrl(…) method. From these parameters, the XML request is built up. For more details, refer to the comments in the included source code.
The generation of XML for the individual parameter sections is delegated to a further set of classes that perform the task of building the XML for a particular API method, validating that all required parameters for that method are present, and generating the MD5 hash used as part of the API security model (see the section below). For example, the Subscriber class encapsulates the subscriber data that forms part of a request (a start session request, for example). It includes getter and setter methods for all the participant parameters, and it includes validation logic to ensure that all the required parameters for that particular method call are present. The getRequestXml(...) method generates the XML data for the session to be included in the PARAMETERS element in the request, and the generateCheck() method generates the hash value to be included in the CHECK element.
The SametimeUnyteService class provides methods to handle generating request URLs to start and join a meeting session, and a method to determine the status of a particular session. For further information on this, and on the full set of objects supplied as part of the Java wrapper around the Meeting Server API that has been created for this component, refer to the sample code included in the Download section of this article, which is extensively commented.
Validation of Meeting Server API requests
The Meeting Server API uses a security hashing procedure to validate the requests and place a time limit of 10 minutes on the lifetime of every request after the point it was generated (a timestamp is included as part of this hashing procedure). Look at the general form of the XML for a start session request (see highlighted sections) shown in listing 4.
Listing 4. General form of the Start Sesssion request XML
<WDAPI ver="1.1" type="call" name="startSession" utc="utc_milliseconds">
<PARAMETERS>
<SUBSCRIBER subscriber_parameters_list />
<VAPI vapi_parameters_list />
<SESSION session_parameters_list />
<PARTICIPANT participant_parameters_list />
<BRAND brand_parameters_list>brand_override_elements_list</BRAND>
</PARAMETERS>
<CHECK check_parameter_list />
</WDAPI>
|
The utc attribute is a timestamp representing the time of generation of the API request. The check section contains hashed versions of the request parameter sections of the XML, in this form shown in listing 5.
Listing 5. Check section of the request XML
<CHECK subscriber="2b0694d2660ab919b6a2d19a061884d3" vapi="2b9694d22667a9acc6a2d19a068884d4" session="2b0694d2660ab919b6a2d19a06188a99" participant="2b9694d2260ab9acc6a2d19a061884d6" brand="2b0694d2660ab919b6a2d19a06188b97"/> |
Each check attribute constitutes an MD5 hash (a message digest algorithm commonly used in generating cryptographic hashes) of the following concatenated string:
<private_token> + <utc_time> + <element_string>
where private_token is a secret token known only to the service provider and trusted API clients, utc_time is the time of request generation (same as the utc attriute on the WDAPI element), and element string is the complete content of the corresponding element. The 128-bit hash result is represented as a string of 32 hex digits.
This hashing procedure is implemented as part of the Java wrapper around the Meeting Server API that is included with as part of this sample. For further details in the implementaton, see the sections in the SametimeUnyteService class that assemble the request XML and the generateCheck(...) method that is implemented in each of the specific parameter handling classes (Subscriber, Session, and so on).
Business component implementation
The business component implementation class is MeetingsBCHandler, located in the com.ibm.quickr.meetings.unyte.bc package. This class fulfills the business component contract with the CAI architecture by implementing interfaces of the CAI Component SPI. Of these interfaces, the most important one for this particular sample is the lifecycle interface. You require instances of this component to maintain a state (to keep track of items such as the current meeting session ID or the description of the previous meeting), and the CAI infrastructure in WebSphere Portal V6.0.1 (on which Lotus Quickr 8.11 is based) does not provide support for maintaining properties at the level of the component instance. As such, individual component implementations need to implement their own storage mechanism for any data associated with the component instance. Typically, this implementation would be stored either in a database or in a JCR document, but for simplicity this sample component uses a file on the file system as the basis for storing a set of name-value pairs that are used to maintain the component state. Each component instance has a separate file associated with it to store its state, which should be created as part of the component instance initialization and cleaned up (that is, deleted) on component destruction. To do this task, you implement methods in the lifecycle interface.
The createInstance(...) method is called on a business component at the point when a new instance of that component is created. Typically this call happens when a portlet that is associated as the view of that business component (see the section below on the relationship between portlets and business components) is added to a place. Listing 6 shows the createInstance(...) method from this sample.
Listing 6. The createInstance(…) method of the component handler
public ListModel createInstance(ListModel parameters) throws ComponentException {
try {
String timestamp = Long.toString(System.currentTimeMillis());
String bcId = "unyte_meeting_" + timestamp;
ConfigFileHelper configHelper = ConfigFileHelper.createConfigFile(bcId);
configHelper.setConfigParam("teamspace.meetings.bc.svcUrl",
"http:/conferenceserver.renovations.com/register/api/main.asp");
configHelper.setConfigParam("teamspace.meetings.bc.svcProv", "SVC_PROV");
configHelper.setConfigParam("teamspace.meetings.bc.billingType", "t");
configHelper.setConfigParam("teamspace.meetings.bc.validationCode", "XXXX");
configHelper.setConfigParam("teamspace.meetings.bc.brandName", "RENOVATIONS");
configHelper.setConfigParam("teamspace.meetings.bc.meetingDesc", "----");
ArrayList list = new ArrayList();
list.add(new VariableImpl("id", "id", "the id", bcId));
return ListModelHelper.from(list);
}
catch(MeetingServiceException mse) {
throw new ComponentException(mse);
}
}
|
The createInstance(...) method in this case performs two important actions. First, it creates the configuration storage file that is used to store the state of this instance. A helper class, ConfigFileHelper (located in the same com.ibm.quickr.meetings.unyte.bc package) is supplied for this purpose. It supports the creation and deletion of configuration files on the file system and the querying and setting of individual properties within those files (for storing the state information). We won't go into the implementation of this here; it's trivial. For further details, refer to that class in the sample code included with this article.
Here, on the component instance creation, you want to fill in some initial configuration details. For the purposes of this sample, we include a copy of the server configuration with each component instance and make it accessible through the edit settings mode of the portlet (see below for further details on this custom mode). In the createInstance(...) method, you want to fill in some defaults for this.
Second, it is the responsibility of the createInstance(...) method to assign an individual identifier to each component instance at the time of creation; this ID must be unique for all instances of that component type. This ID is a required list element in the ListModel that must be returned from this method, and it is used to identify the component instance in subsequent CAI interactions.
The removeInstance(...) method is called after component instance destruction, which occurs when a portlet that is linked to the component is removed from a place or when the containing place is deleted. This method allows a component instance to perform custom cleanup actions. Note that this method is called asynchronously; there is a component cleanup daemon that runs on the server once daily and calls this method on any component instances that have been deleted since the last time it ran. This process also runs on server startup. In the case of your sample component, you want the configuration file relating to the deleted component to be deleted from the file system as shown in listing 7.
Listing 7. The removeInstance(…) method of the component handler
public void removeInstance(String bcId) throws ComponentException {
try {
ConfigFileHelper.deleteConfigFile(bcId);
}
catch(MeetingServiceException mse) {
throw new ComponentException(mse);
}
}
|
The templatable interface covers the serialization of the component state into and out of templates. A template is an XML document containing a blueprint for the creation of an application (place) including the details of all its contained components and the relationships between them.
The serializeToTemplate(...) method allows the component in a place to record its state for inclusion in the template XML when the place is saved as a template. This sample does not include any custom serialization handling (though it could be altered, for example, to serialize the meetings service configuration part of the component state).
When a new component instance is created as the result of a new place being created from a template that contains the component, the createFromTemplate(...) method is called. This method is passed in the data that was serialized to the template during the serializeToTemplate(...) method. Although no data was serialized, the sample must implement this method to cover the component initialization when the instance is created as part of a template. This is the same initialization as performed in the Lifecycle.createInstance(...) method; a new configuration document must be created to hold the state of the component instance. As with the Lifecycle.createInstance(...) method, the Templatable.createFromTemplate(...) method must generate and return a unique identifier for the new component instance.
Other CAI component SPI interfaces
This component does not implement any of the other component interfaces (the lifecycle and templatable interfaces are required as detailed above). Use of the other CAI component interfaces is optional. A component that needed to expose component roles and manage resource access according to those roles would need to implement the membership interface, for example, but the interface can be omitted if it is not required by the component. For further information about the CAI component interfaces, see the interface Javadoc located at <quickr>/PortalServer/doc/Javadoc/spi_docs.
Component-specific methods of the business component
In addition to implementing required CAI component SPI interfaces, business components also typically expose a set of methods to support the business logic of the component. In the case of this sample, these methods relate to the management of Lotus Sametime Unyte meetings: starting, joining, and getting the status of Web conference sessions. These methods are used by the portlet that provides the front end to the business component. The MeetingsBCHandler class includes these methods; they are grouped together after the CAI Component SPI interface implementation. For more detail about what these methods do, see the comments in the accompanying source code.
Registering the component with the framework
CAI business components use a plug-in architecture, implemented using the WebSphere Application Server extension registry system for managing server extensions. To deploy a business component, the implementation classes need to be made available on the system classpath with a plug-in descriptor. Because the only way of introducing a business component into an application now is by including a portlet that is linked to the business component (as its view; see the section below), package the business component binaries and plug-in descriptor with the portlet into a WAR file and deploy that on the server. Then the extension can be picked up by the registry, and the business component implementation is available in the same scope.
To achieve this registration for our sample, the plugin descriptor is packaged into the JAR that contains the business component (and the portlet) classes. See plugin.xml in the included sample code and the code shown in listing 8.
Listing 8. Plug-in declaration for the sample component
<?xml version="1.0" encoding="UTF-8"?>
<plugin id="com.ibm.quickr.meetings.unyte" version="8.1.1">
<extension point="com_ibm_portal_app.BusinessComponents" id="UnyteMeetingsBC">
<provider class="com.ibm.quickr.meetings.unyte.bc.MeetingsBCHandler">
</provider>
</extension>
</plugin>
|
In particular, note the bolded code. The extension point that all CAI business components need to associate themselves with is com_ibm_portal_app.BusinessComponents. Every extension also needs an ID, composed of two parts, the plug-in ID and the extension ID. Finally, the business component implementation class is specified as the extension provider.
Associating the business component with a portlet
Business components are introduced into places by linking a portlet to them. That portlet then acts as a view on the business component. To create this association, a portlet preference is used to supply a reference to the business component that is to be associated with the portlet. The name of this preference is com.ibm.portal.bc.ref, and it is specified in the portlet.xml file, as shown in listing 9.
Listing 9. Portlet descriptor for the sample component
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app id="UnyteMeetingsPortletApp1"
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
<portlet>
<portlet-name>SametimeUnyteMeetingsPortlet</portlet-name>
<display-name>Sametime Unyte meetings portlet</display-name>
<portlet-class>
com.ibm.quickr.meetings.unyte.portlet.SametimeUnyteMeetingsPortlet
</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
<portlet-mode>edit_defaults</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<portlet-info>
<title>Sametime Unyte meeting</title>
</portlet-info>
<portlet-preferences>
<preference>
<name>com.ibm.portal.bc.ref</name>
<value>portal:extreg/com.ibm.quickr.meetings.unyte.UnyteMeetingsBC
</value>
</preference>
</portlet-preferences>
</portlet>
<custom-portlet-mode>
<portlet-mode>edit_defaults</portlet-mode>
</custom-portlet-mode>
</portlet-app>
|
When an instance of the portlet is introduced into a place, this preference is detected by the CAI runtime, and it knows to instantiate a new business component instance and associate it with the portlet. Note that the format of the component identifier consists of the portal:extreg/ prefix (which specifies that the component can be loaded from the extension registry) and the component ID, which consists of the plug-in ID (com.ibm.quickr.meetings.unyte in this case) and the extension ID (UnyteMeetingsBC) separated by a dot.
The portlet included in this sample is a simple JSR 168 portlet. It supports two modes, a main view mode for rendering the UI to display meeting status and allow users to start and join meetings, and an edit settings mode to allow users to change some parts of the component configuration (the service provider details). The SametimeUnyteMeetingsPortlet class in the com.ibm.quickr.meetings.unyte.portlet package is the portlet implementation class.
If you open the included workspace, the layout of the portlet resources can be seen as shown in figure 4 (bordered in the two boxes in the figure):
Figure 4. Component layout highlighting portlet resources
Rendering the main portlet view
The view JSP (mainView.jsp) is passed in the session state by a session bean and then renders a UI accordingly. One aspect to note is the behavior when a meeting has just been started or joined. In these cases, JavaScript is included that opens the Meeting Server API URL (either for a startSession or a startParticpiant call) and then sets a timer after which the meeting should have finished initialization and a page reload is triggered. The rest of the JSP is fairly standard; for simplicity in the sample, styles and JavaScript have been included inline whereas the more typical approach would be to externalize these pieces. For further information, refer to the JSP source in the included sample code.
Supporting the custom edit settings mode
Lotus Quickr uses a custom portlet mode to support the edit settings action that is available in the Lotus Quickr portlet skin. The sample portlet uses this mode to present a form, allowing the user to edit the meeting service configuration for the component instance (the service host details, for example) and have this saved back into the component. To support this custom mode, which is used by Lotus Quickr, first the mode must be declared as a custom mode supported by the portlet; this step is done by including a reference to it in the portlet.xml as shown in listing 10.
Listing 10. Adding support for the edit_defaults mode in the portlet descriptor
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app id="UnyteMeetingsPortletApp1"
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd">
<portlet>
<portlet-name>SametimeUnyteMeetingsPortlet</portlet-name>
<display-name>Sametime Unyte meetings portlet</display-name>
<portlet-class>
com.ibm.quickr.meetings.unyte.portlet.SametimeUnyteMeetingsPortlet
</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
<portlet-mode>edit_defaults</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<portlet-info>
<title>Sametime Unyte meeting</title>
</portlet-info>
<portlet-preferences>
<preference>
<name>com.ibm.portal.bc.ref</name>
<value>portal:extreg/com.ibm.quickr.meetings.unyte.UnyteMeetingsBC
</value>
</preference>
</portlet-preferences>
</portlet>
<custom-portlet-mode>
<portlet-mode>edit_defaults</portlet-mode>
</custom-portlet-mode>
</portlet-app>
|
Then, the portlet implementation class must define this customization as a portlet mode, and check for it as part of the doDispatch(...) method (which must be overridden) as shown in listing 11.
Listing 11. Defining the portlet mode
public static final PortletMode EDIT_DEFAULTS_MODE =
new PortletMode("edit_defaults");
protected void doDispatch(RenderRequest request, RenderResponse response)
throws PortletException, java.io.IOException {
WindowState state = request.getWindowState();
if (!state.equals(WindowState.MINIMIZED)) {
PortletMode mode = request.getPortletMode();
if (mode.equals(PortletMode.VIEW)) {
doView(request,response);
}
else if (mode.equals(PortletMode.HELP)) {
doHelp(request, response);
}
else if (mode.equals(EDIT_DEFAULTS_MODE)) {
doEditDefaults(request,response);
}
}
}
|
Finally, the doEditDefaults(...) method dispatches the request to a JSP (editConfig.jsp), which presents a form allowing the user to enter new values for the meeting service configuration.
The portlet uses a session bean to maintain and pass around the portlet and component state. This bean is initialized once (on first accessing the portlet within a session) with some basic information that remains constant for the portlet session. This initialization takes place in the initializeSessionBean(...) method. Information about the current user and about the business component is recorded. The portlet needs to know two things about the business component. First, it needs get a handle to the component so that it can access its business methods by reading the portlet preference that identifies the associated business component (see section above). See listing 12.
Listing 12. Obtaining the component handler by using the portlet preference
private MeetingsBCHandler getBC(PortletRequest request) {
try {
Context ctx = new InitialContext();
Object bc = ctx.lookup(request.getPreferences().getValue
("com.ibm.portal.bc.ref", ""));
return (MeetingsBCHandler)bc;
}
catch (NamingException e) {
e.printStackTrace();
return null;
}
}
|
Because JNDI lookups are expensive operations, the reference to the BC is looked up once, then stored for the rest of the session. For simplicity here, we have looked up our component handler and then cast it to the implementation class type that we know. A more typical approach is to have a separate interface specifying the component's business methods through which client code would access the component.
For CAI business components, an object of the handler class is not created for each component instance; instead, every method on the component handler requires the component instance ID as a required parameter. For this reason, for the portlet to access the component, it needs to determine the ID of the business component instance associated with this particular portlet instance. This determination is achieved by reading another portlet preference, which is written by the infrastructure as part of the component initialization. This preference is com.ibm.portal.bc.instance.id. It is set to the ID value that was generated by the component during the createInstance(...) or (createFromTemplate(...) method (depending on whether the component instance was created as the result of a portlet being added to the place or as the result of a template being instantiated):
String bcId = request.getPreferences().getValue("com.ibm.portal.bc.instance.id", "");
sessionBean.setAttribute("bcId", bcId);
Three possible actions can occur in the portlet, which are handled as usual in the processAction(...) method of the portlet implementation class. The first action is the meeting session initiation, which occurs when users fill in the subscriber details form and click the Start Meeting button. In this case, the values of the form are read and recorded in the session bean and the component state, a timestamp is recorded on which the session initialization timeout is based, and the state is modified to reflect that a meeting has been started (see the section on state variables below). The second action occurs when users join a meeting. In this case, a timestamp needs to be recorded on which the meeting UI initialization timeout is based. Finally, there is an action that confirms that the service configuration options have been changed while the portlet was in edit settings mode (see the section below). In this case, the new meeting service configuration is recorded in the component, and the view mode is set back to view. For further details, see the processAction(...) method implementation.
Determining the meeting session state
The portlet uses a number of state variables to control the flow of execution and determine which UI is rendered. These state variables are stored centrally by the business component (because the state needs to be shared across multiple portlet sessions belonging to different users) and read from there into the session bean, which is then passed around the portlet (for example, the UI uses this session bean to determine what it renders). A list of these state variables is shown in table 2.
Table 2. State variables
| State variable | Function |
|---|---|
| viewState | This variable records the state of the meeting session and is used to determine what to render for the portlet view. There are a number of possible states, including: active (a meeting session is active); inactive (a meeting session is not currently active but a previous meeting has taken place); initial (a meeting session is not currently active and no previous meeting has occurred with this component); waiting for start (a meeting session has been started but the Web conference has not yet finished initialization so the session ID is not known and no session details can be retrieved); and the waiting for join state (current users are waiting for the Web conference UI to initialize so that they can participate in the meeting). |
| lastSessionId | The session ID of the last meeting associated with this component. When a session is not active, this ID is used to display details of the last meeting that took place. |
| lastSessionSubscriberId | The subscriber ID of the last meeting associated with this component (in combination with the last session ID, this is required to retrieve the details of the last meeting session if that needs to be displayed). |
| lastMeetingDesc | The description of the last meeting session. |
| currentSessionId | The session ID of the current session when a session is active. |
| currentSessionSubscriberId | The subscriber ID that was used to start the current session when a session is active. |
| meetingDesc | The description of the current meeting when a session is active. |
| lastStartActionTime | A record of the time at which the last meeting was initiated. This record is needed because the portlet needs to allow a certain amount of grace time while waiting for a meeting session to initialize. After this timeout, if the session still does not exist, it is assumed to have failed initialization or to have been aborted; the component then returns to an inactive state. |
| lastJoinActionTime | A record of the time at which the current user took the action to join the currently active meeting session. This record is used to manage the UI while the user waits for the conference session to start in participant mode. There is a timeout after which the conference session initialization is assumed to have failed or been aborted, and the UI then reverts to the session active display where the current user is not participating (that is, the Join button display). |
As part of rendering the view, the getSessionInfo(...) method is called in the portlet implementation class; it is here that the state is updated (and recorded back into the component and the session bean). See the getSessionInfo(...) method for details of how the state-determining logic is implemented. The request is then dispatched to the view JSP (mainView.jsp) to render the UI based on the current session state.
Adding the component to the Lotus Quickr component palette
The final step in creating your new Lotus Quickr component is to make it available in the component palette that displays when customizing places as shown in figure 5.
Figure 5. Sample component displayed in the Lotus Quickr component palette
Follow these steps:
- Define the strings for the component entry. These strings are localized and are located in a set of properties files. Open the file
<quickr>\PortalServer\shared\app\nls\quickr_en.propertiesand an extra two strings, one for the component title and one for the description:
unyte.meeting=Unyte Meeting
new.unyte.meeting=New Unyte Meeting - Save and close the file. This step gives us strings for the English language only. If other languages are to be supported, the corresponding language variants of the properties file must also be edited.
- Add the graphic for the component entry. This image, unyteMeeting.gif, is included with the sources for this sample. Place this image into the following directory:
<quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\images\quickr - Set up some styles. Open this file:
<quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\styles_theme.jspfNavigate to the section labeled "customize drop down" and copy the two style definitions used by the calendar component in the palette. Then, change the IDs and the image file referenced, as shown in listing 13.
Listing 13. Adding styles for the component palette entry
div.actionSelect li.unyteMeeting{
background: ${colors.actionSelectBackground} url("images/quickr/unyteMeeting.gif")
${requestScope.cssRules.bidiLeft} 50% no-repeat;
}
#teamSpaceAddComponentForm h2.unyteMeeting{
background: ${colors.actionSelectBackground} url("images/quickr/unyteMeeting.gif")
${requestScope.cssRules.bidiLeft} 50% no-repeat;
}
|
- Save and close the file.
- Next, you need to add the component entry into the palette. Open the following file:
<quickr>\wp_profile\installedApps\<node>\wps.ear\wps.war\themes\html\QPG\pageHeaderContent.jspSearch for a div with the ID customizePage2. Beneath this div is the set of component entries for the second page in the palette. You use one of the existing entries as a base from which to work.
- Copy the <li> element, which represents the calendar component entry, and paste a new copy of that section directly after the calendar. Now change the class to match the style that was created in the last step.
- Change the strings to match those you created for your component. These are looked up from a resource bundle so it’s the keys within that bundle that you need to specify.
- The window that adds the component uses the portlet’s unique ID to add it into the place. This needs to be passed into the showTeamSpaceAddComponentForm(…) function call. Now you have the code shown in listing 14.
Listing 14. Adding the new entry into the component palette
<li class="unyteMeeting">
<a href="#"
onClick="javascript:showTeamSpaceAddComponentForm("
<%=MarkupUtil.htmlAttributeEscape(MarkupUtil.jsEscape(pageHeaderText.getString
("unyte.meeting")))%>", "<%=MarkupUtil.htmlAttributeEscape
(MarkupUtil.jsEscape(pageHeaderText.getString("new.unyte.meeting")))%>
", "<%= applicationID %>", "wps.p.unyteMeeting",
true, nodesOnLevel, true, 'unyteMeeting')"
class="picture">
<portal-fmt:text key="unyte.meeting" bundle="nls.quickr"/>
</a>
</li>
|
After these changes are complete, you need to touch the theme JSP files that include the .jspf fragments that were edited, to force a recompile operation that includes the changes. This task is accomplished by saving the JSP (without editing; you need to change the modification timestamp). The files that need to be saved in this way are styles.jsp and Default.jsp.
In this article we show you a simple way to integrate Sametime Unyte services in Lotus Quickr places. The Lotus Quickr place with the Lotus Sametime Unyte functionality allows users to initiate Web conferencing or schedule a Web conference right from the applications that they use every day.
The main goal of using the Web conferencing software is to allow people to talk and share information with anyone, anytime, anywhere. It helps organizations to make faster decisions and save expenses by reducing travel.
In summary, Lotus Sametime Unyte Web conferencing embedded in Lotus Quickr can help IBM customers do the following:
- Resolve issues and ask and answer questions through high-quality communication channels
- Build networking and create relationships with colleagues around the world
- Reduce or even eliminate the need for travel while enabling instant and global collaboration within organizations
- Integrate cutting-edge audio, video, and telephony technologies to help meetings run smoothly
| Name | Size | Download method |
|---|---|---|
| UnyteMeetingComponent.zip | 73KB | HTTP |
Information about download methods
Learn
-
Learn more about IBM Lotus Quickr - team collaboration software.
-
Watch the IBM Lotus Quickr 8.1.1 sample templates videos on YouTube.
-
Contribute to the Lotus Quickr wiki.
-
Read more about IBM Lotus Quickr on wikipedia.
-
Learn more about IBM Lotus Sametime Unyte: Integration Overview.
-
Learn more about getting started with IBM Lotus Sametime Unyte Meeting.
-
Learn more about IBM Lotus Sametime Unyte Web Conferencing Services.
-
Learn more about LotusLive Meetings.
-
Learn more about IBM Lotus Sametime application integration.
-
Learn more about the Web conferencing capabilities in IBM Lotus Sametime software.
-
Read the developerWorks article, "Building composite applications and templates in WebSphere Portal V6."
-
Read the wiki article, "Bring information and collaboration into the context of your daily business processes using composite applications."
Get products and technologies
-
Download the IBM Lotus Quickr 8.1.1 sample templates.
Discuss
Stephen Wills is a software engineer working at the IBM Dublin Software Lab in Ireland. He works on the IBM Lotus Quickr product, particularly in the area of templating. You can reach Stephen at stephen_wills@ie.ibm.com.
Ramajeyam Gopalraj is an Advisory Software Engineer at IBM working with the Lotus Quickr team in Research Triangle Park, NC. You can reach Ramajeyam at ramajeyam_gopalraj@us.ibm.com.




