Enabling portlets for cooperation


This section describes how to enable portlet applications to use publish properties to the property broker for interactive communication. Use a combination of declarative and programmatic approaches to enable your portlets for cooperation. Both methods are demonstrated in this section. The declarative approach is simpler to implement, but the programmatic approach allows your portlet to take advantage of more advanced features provided by the property broker. You should be familiar with basic portlet programming concepts before working with the APIs provided by the property broker. The property broker programming model builds on the portlet programming model.

 

Cooperative portlet samples

WebSphere Portal includes some sample portlet applications that interact with each other through the property broker.

Both of these sample portlet applications demonstrate the capability to save property transfers as wires between portlets when users use the Ctrl-click combination on the icon or image map. The samples can be found in the wp_root/installableApps directory. Source is included in the WAR file.

For more information, see Cooperative portlet samples.

 

Enabling source portlets using the declarative approach

Source portlets can publish their output properties by inserting tags from a custom JSP library in their JSPs. A JSP tag library is provided to allow source properties to be identified in JSPs. These tags are available by including the c2a.tld tag library in the JSP as follows.

<@ taglib uri="/WEB-INF/tld/c2a.tld" prefix="c2a" %>

As with other portlets, also include the portlet tag library and the <portletAPI:init/> tag in the head of the JSP:

  <%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
  <portletAPI:init/>

Note that c2a.tld is installed as part of WebSphere Portal and should not be packaged in the portlet WAR file.

Two JSP tags can be used to declare output properties in the source portlet:

For detailed information about using these tags, see JSP tags used by source portlets

 

<c2a:encodeProperty/> Example

The following example comes from the source for OrderDetailView.jsp. An HTML table is generated that includes a column for the Tracking ID, which is retrieved from the OrderDetailBean. Next to the Tracking ID value is the <c2a:encodeProperty/> tag, which uses the Tracking ID as the value of the data to be sent with the icon is clicked. The name attribute is strongly encouraged, as without it, the interactive approach to creating wires (pressing Ctrl while clicking on the Click-to-Action icon) is not available. The name must identify a property on the source end of the wire that has been registered with the property broker. You can register the property either programmatically or declaratively.

Programmatic registration of output properties

Use the registerProperties() method, as described in Enabling source portlets using the programmatic approach.

Declarative registration of output properties

The property must be associated with an action declared in the WSDL file, as described in Enabling target portlets using the declarative approach. The declarative approach is simpler. If the source portlet does not have any actions to expose for invocation through other portlets, an empty action with no input parameters and multiple output parameters may be defined. The declared output parameters cause the corresponding output properties to be registered.

When designing pages which may contain C2A icons and select boxes, position the icons so that the pop-up menus they generate do not occupy the same area of the page as a select drop-down box. This is to overcome a bug in Internet Explorer that causes a select drop-down box to obscure the pop-up menu when the icon is clicked. This occurs because Internet Explorer uses the operating system drop-downs that have higher priority than CSS/DHTML rendering used for the menu.

Notice that the <FORM> tag has an text input field that accepts the ORDER_ID as input from the user. Thus, the portlet can receive the action either by user input on the JSP form or through Click-to-Action.



...

        <C2A:encodeProperty name="trackingId" 
            namespace="http://www.ibm.com/wps/c2a/examples/shipping" 
            type="TrackingIDType" value="<%= od.getTrackingId() %>" />
    

<div dir="ltr">
<%
    OrderDetailBean odb = (OrderDetailBean) request.getAttribute(OrderDetailPortlet.ORDER_DETAIL_BEAN);
%>
    <table border="0" cellspacing="0" cellpadding="3">

      <tr class="wpsTableHead">
        <td>Order_ID</td>
        <td>SKU</td>
        <td>Quantity</td>
        <td>Status</td>
        <td>Tracking_ID</td>
      </tr>

      <tr class="wpsTableShdRow">
      <%  OrderDetail od = odb.getOrderDetail(); %>

        <td><%= od.getOrderId() %></td>
        <td><%= od.getPartNo() %></td>
        <td><%= od.getUnits() %></td>
        <td><%= od.getStatus() %></td>
        <td>
           <%-- The name must match what was declared in the wsdl file or
               in the property/action registration --%>
           <C2A:encodeProperty 
              name="trackingId" 
              namespace="http://www.ibm.com/wps/c2a/examples/shipping" 
              type="TrackingIDType" 
              value="<%= od.getTrackingId() %>" />
           <%= od.getTrackingId() %>
        </td>
      </tr>
    </table>
     
    <div class="wpsPortletText">
       <FORM method="POST"
             action="<%= odb.getActionURI() %>"
             enctype="application/x-www-form-urlencoded"
             name="<portletAPI:encodeNamespace value='OrderDetails' />">
          <LABEL class="wpsLabelText" 
                 for="<portletAPI:encodeNamespace value='<%= OrderDetailPortlet.ORDER_ID %>' />">
             Enter order id:
          </LABEL>
          </BR>
          <INPUT class="wpsEditField" 
                 name="<portletAPI:encodeNamespace value='<%= OrderDetailPortlet.ORDER_ID %>' />" 
                 type="text"/>
                 </BR>
          <INPUT class="wpsButtonText" 
                 name="<portletAPI:encodeNamespace value='submit' />" 
                 type="submit" 
                 value="Submit"/>
       </FORM>
    </div>
</div>

...


 

<c2a:encodeProperties> Example

The following example is based on the source for OrderMonthView.jsp. Markup is generated by the enclosed <c2a:encodeProperty/> tags because of the value of generateMarkupWhenNested.



...
      <% 
        Order[] orders = omb.getOrders();
        for (int i = 0; i < orders.length; i++) { %>

      <tr class="<%=((i % 2) == 0)?"wpsTableShdRow":"wpsTableRow"%>">

         <C2A:encodeProperties caption="Send.row.to.all" 
            description="Send.row.data.to.all.portlets">

        <td>
            <C2A:encodeProperty name="orderId"
               namespace="http://www.ibm.com/wps/c2a/examples/shipping" 
               type="OrderIDType" value="<%= orders[i].getOrderId() %>" 
               broadcast="true" generateMarkupWhenNested="true"/>
            <%= orders[i].getOrderId() %>
        </td>

        <td>
            <%-- only a single match is expected, but setting broadcast="true" 
               tests a different branch in the runtime code --%>
            <C2A:encodeProperty name="customerId"
               namespace="http://www.ibm.com/wps/c2a/examples/shipping" 
               type="CustomerIDType" 
               value="<%= orders[i].getCustomerId() %>" 
               broadcast="true" 
               generateMarkupWhenNested="true"/>
            <%= orders[i].getCustomerId() %>
        </td>

        <td>
         </C2A:encodeProperties>
   <%= orders[i].getStatus() %>
        
        </td>
        
      </tr>

      <% } %>


...


 

Enabling source portlets using the programmatic approach

The source portlet can also use the programmatic approach to publishing its properties by using the registerProperties() and registerActions() methods from the PropertyBrokerService interface. Properties associated with actions are automatically registered in the registerActions() call. Usually, only portlets that implement PropertyListener need to register the properties separately using setProperties().

Actions and properties remain registered persistently until such time that the portlet is removed from deployment, redeployed, or unregistered programmatically using the unregisterProperties() and unregisterActions() methods. Usually, unregistration may be omitted as the runtime will clean up when a portlet is uninstalled.

The following sample shows the registerProperties() method implemented by Weather Portlet in the destination guide sample.

Programmatically registering properties



   private void registerPropertiesIfNecessary(PortletRequest request) 
   throws PropertyBrokerServiceException {

       PortletSettings settings = request.getPortletSettings();
       Property[] properties = pbService.getProperties(request, settings);
       if (properties == null || properties.length == 0) {
      PortletContext context = getPortletConfig().getContext();
      Locale[] locales = new Locale[] {Locale.US};

      //not registered, register now
      properties = new Property[1];
      properties[0] = PropertyFactory.createProperty(settings);
      properties[0].setName("City");
      properties[0].setDirection(Property.IN);
      properties[0].setType("CityType");
      properties[0].setNamespace(NAMESPACE);
      properties[0].setTitleKey("Weather.City");
      properties[0].setDescriptionKey("City.whose.weather.info.will.be.shown");
      properties[0].setLocalizationInfo(NLSFILENAME, context, locales);

      pbService.registerProperties(request, settings, properties);

       }
   }


The ClickMap portlet demonstrates how the JSP can implement its own custom icon for launching Click-to-Action events. Rather than using the <C2A:encodeProperty/> tag, ClickMap.jsp uses a getActionTriggerMarkup() method from the PropertyBrokerService interface to implement a clickable map that sends properties to the broker.

Generating custom visual controls for properties



<MAP NAME="Cities">
<%
   CityData[] cities = CLICK_MAP_BEAN.getCities();   
   PropertyBrokerService broker = CLICK_MAP_BEAN.getPropertyBroker();
   String imageSource = portletResponse.encodeURL("GIFS/USA.gif");
   if (cities != null)
   {
      for(int counter = 0; counter < cities.length;   counter++)   
      {

         if (cities[counter].getCityName().equals(CLICK_MAP_BEAN.getCurrentCity())) {
         
            imageSource = portletResponse.encodeURL("GIFS/"+cities[counter].getFileName());
               
         }

         Property property = PropertyFactory.createProperty(
         portletRequest.getPortletSettings());
         PropertyValue propValue = null;
         try {
         property.setNamespace("http://www.ibm.com/wps/basetypes");
         property.setType("CityType");
         property.setName("outCity");
         property.setDirection(Property.OUT);//The OUT direction is important
         propValue = PropertyFactory.
         createPropertyValue(property, cities[counter].getCityName());
         } catch (Exception e) {
         System.out.println("Unexpected exception in ClickMap.jsp:" + e);
         }

         ActionTriggerMarkup shm = null;
         if (broker != null) {
            shm = broker.getActionTriggerMarkup(portletRequest, portletResponse,
                                       propValue, true);
         }
         String onClickMarkup = "";
         String showActionsMarkup = "";
         boolean isWired = false;
         if (shm != null) {
            onClickMarkup = shm.getOnClickMarkup();
            showActionsMarkup = shm.getShowActionsMarkup();
            isWired = shm.preWiredActions();//can use this
            //to provide different visual cue, not used here
         }

%>

   <%=showActionsMarkup%>
       <AREA shape="circle"   
         coords="<%=cities[counter].getX()%>,<%=cities[counter].getY()%>,5"   
         alt="<%=cities[counter].getCityName()%>"
        href="#"
        onclick="<%=onClickMarkup%>"></AREA>
<%
      }
   }
%>
    <AREA shape="default" nohref=>
</MAP>




 

Enabling target portlets using the declarative approach

The code for the target portlet class must meet the following requirements:

The following shows the actionPerformed() method of the OrderDetailPortlet.java in the Shipping demo. This portlet accepts the ORDER_ID parameter in its actionPerformed() method. This parameter corresponds to an input parameter in the binding section of the portlet's WSDL file (OrderDetailC2A.wsdl in this example).

 

Sample Order Details code



...

   private static final String PREFIX = "";
   public static final String ORDER_ID = PREFIX + "orderId";
   public static final String TRACKING_ID = PREFIX + "trackingId";

...
    
    public void actionPerformed (ActionEvent event) 
    {
//        DefaultPortletAction action = (DefaultPortletAction) event.getAction();
   String actionName = event.getActionString();

        PortletRequest request = event.getRequest();

        //An action causes the state to be modified
        ShippingUtils.setLastModified(request);

       if( getPortletLog().isDebugEnabled() ) {
           getPortletLog().debug("OrderDetailActionListener - Action called");
       }
       
        if (actionName.equals(ORDER_DETAILS)) {
            request.getPortletSession().setAttribute(ACTION_NAME, ORDER_DETAILS);
            request.getPortletSession().setAttribute(ORDER_ID, request.getParameter(ORDER_ID));

       //We do this as tracking id is an out param in the C2A WSDL file
       //We write the tracking id in the request so it can be published by
       //the broker in the same event cycle
            String orderId = (String) request.getPortletSession().getAttribute(ORDER_ID);
       OrderDetail od = ShippingDB.getOrderDetail(request.getParameter(ORDER_ID));
            request.getPortletSession().setAttribute(ORDER_DETAIL, od);
       request.setAttribute(TRACKING_ID, od.getTrackingId());

        }  else if (actionName.equals(ORDER_ID_ENTRY)) {
            request.getPortletSession().setAttribute(ACTION_NAME, ORDER_ID_ENTRY);
   }
    }
...


 

Declaring actions for target portlets

Portlets actions which can accept data transferred using the property broker may be declared in a WSDL file. The alternative is to register the actions programmatically. WSDL is a language used to define collections of abstract operations, together with the input and output parameters (called parts in WSDL) for each operation. The syntax used is standard WSDL. However, some extensions are introduced specifically for cooperative portlets. These extensions follow the extension rules allowed by the base WSDL schema. A description of the WSDL schema and examples can be found at http://www.w3.org/TR/wsdl. For more information about the cooperative portlet extensions, see WSDL reference for cooperative portlets.

A concrete implementation of the abstract operation definitions may be specified using bindings. The binding thus includes the protocol and the data format to be used for invoking the operations - in essence, all the information an invoking program would need to invoke a concrete implementation of the operations. WSDL is an extensible language that allows new bindings to be introduced to specify new ways of implementing the abstract operations. Cooperative portlets use a custom binding.

Certain restrictions must be followed by the operation declarations for cooperative portlets. At present, only a single input parameter is allowed for each operation. The types associated with parameters must be declared using XSD (see the XSD specification).

In contrast, you can declare any number of output parameters for a target portlet. Output parameters are used to allow the target to react to an action by transferring data to other portlets, creating a chained propogation that updates multiple portlets from a single action. (The propagation occurs only if a suitable wire is present.) If the portlet action changes or accesses other state variables as a result of the action, it may be appropriate to declare them as output parameters. The portlet programmer may need to distinguish portions of the data model for the portlet which could be of interest to other portlets. These are often appropriate candidates for output parameters. For example, as a consequence of executing an "Order Detail" action, a portlet may retrieve information which contains an associated tracking ID. Since this is a key which may potentially be of interest to other portlets, it may be declared as an output parameter.

A custom binding section must also be used to specify how to invoke the declared operations on the implementing portlet. The binding section maps each abstract operation to an action on the portlet. For each operation, the portlet action name must be provided. For each operation parameter, the action parameter name must be provided. Further, an attribute may be used to specify where the parameter will be bound. Choices are request parameter, request attribute, session attribute, or action attribute. (The action attribute is deprecated.) Reasonable defaults apply for most attributes. An example WSDL file for C2A is shown below. Note that the namespace and type associated with the orderId parameter match the source declaration using the encodeProperty tag. Hence, if the corresponding source and target portlets were placed on the same page, the property broker would detect a match.

 

WSDL Example

An example segment of code is provided below from OrderDetailC2A.wsdl. The orderId parameter used in the OrderDetailsPortlet class is declared as an input parameter in the binding. In addition, the tracking ID is declared as an output parameter to be transferred to other target portlets when this action is received.



<?xml version="1.0" encoding="UTF-8"?>
<definitions name="OrderDetail_Service"
  targetNamespace="http://www.ibm.com/wps/c2a/examples/shipping"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:portlet="http://www.ibm.com/wps/c2a"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:tns="http://www.ibm.com/wps/c2a/examples/shipping"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
              xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xsi:schemaLocation="http://schemas.xmlsoap.org/wsdl/ http://schemas.xmlsoap.org/wsdl/ 
             http://www.w3.org/2001/XMLSchema http://www.w3.org/2001/XMLSchema.xsd" 
>

<types>
  <xsd:simpleType name="OrderIDType">
    <xsd:restriction base="xsd:string">
    </xsd:restriction>
  </xsd:simpleType>
</types>

<types>
  <xsd:simpleType name="TrackingIDType">
    <xsd:restriction base="xsd:string">
    </xsd:restriction>
  </xsd:simpleType>
</types>

<message name="OrderDetailsRequest">
  <part name="order_Id" type="tns:OrderIDType"/>
</message>

<message name="OrderDetailsResponse">
  <part name="tracking_Id" type="tns:TrackingIDType"/>
</message>

<portType name="OrderDetail_Service">
  <operation name="order_Detail">
     <input message="tns:OrderDetailsRequest"/>
     <output message="tns:OrderDetailsResponse"/>
  </operation>
</portType>

<binding
    name="OrderDetailBinding"
      type="tns:OrderDetail_Service">
  <portlet:binding/>
  <operation name="order_Detail">
    <portlet:action name="orderDetails" type="simple" caption="Order.Details" description="Get.details.for.specified.order.id"/>
    <input>
      <portlet:param name="orderId" partname="order_Id" caption="order.id"/>
    </input>
    <output>
      <portlet:param name="trackingId" partname="tracking_Id" boundTo="request-attribute" caption="tracking.id"/>
    </output>
  </operation>
</binding>
</definitions>



 

Enabling target portlets using the programmatic approach

Registration of actions and properties can be done programmatically using the registerProperties() and registerActions() methods from the PropertyBrokerService interface. Instead of delivering property changes through portlet action invocations, changes may also be delivered using the setProperties() method of the PropertyListener interface. This can happen if the target end of the wire connects to a target property, rather than a target parameter, or if the delivery is triggered by the user through the visual control. In this event, the property broker may deliver multiple property values in a single invocation of setProperties(). The same property may appear multiple times in the array passed. Also, setProperties() is only invoked during the event phase, and all property changes which the portlet is eligible to receive are guaranteed to be delivered by the end of the event phase (as indicated by the invocation of the endEventPhase() callback in the EventPhaseListener interface).

The following example shows the setProperties() method from ClickMapPortlet.java. In this example, the input property newCity is received and passed to the ClickMapBean to set the CurrentCity property. However, no action is taken with CurrentCity. Instead, a new output property is created and passed to the broker using changedProperties(). Notice that the class must implement the PropertyListener interface.


public class ClickMapPortlet extends PortletAdapter
       implements EventPhaseListener
{

...


   public void setProperties(PortletRequest request, PropertyValue[] properties) 
   {
      PortletSession session = request.getPortletSession();
      PortletSettings settings = request.getPortletSettings();

      ClickMapBean bean = (ClickMapBean) session.getAttribute("CLICK_MAP_BEAN");
         
      try 
      {
         String newCity = null;
         for (int i = 0; i < properties.length; i++) {
            Property p = properties[i].getProperty();
            if (p.getName().equals("newCity")) {
               newCity = (String) properties[i].getValue();
            }
         }
      
         if (newCity != null && bean != null) 
         {
            String[] names =   {   "City Name"   };
            Object[] values =   {   newCity      };
            bean.setCurrentCity(newCity);               
            publishCity(request, newCity);
         }
         
      } catch (Throwable e) {
         log.text(Logger.ERROR, "setProperties",
            "Unexpected exception", e);
      }
   }
   

 

Create the deployment descriptors

The web.xml and portlet.xml files must be modified to enable portlet cooperation. You must modify web.xml to refer to the property broker classes. The servlet class entry should specify the com.ibm.wps.pb.wrapper.PortletWrapper class in the property broker. The original portlet application class should also be specified using the c2a-application-portlet-class initialization parameter.

Sample web.xml file


      
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 
              "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
   <web-app id="ShippingPortletsC2A">
      <display-name>Shipping Portlets - C2A Version</display-name>
      <servlet id="OrderDetailC2A">
         <servlet-name>Order Details - C2A Version</servlet-name>
         <servlet-class>com.ibm.wps.pb.wrapper.PortletWrapper</servlet-class>
    <init-param>
             <param-name>c2a-application-portlet-class</param-name> 
             <param-value>com.ibm.wps.portlets.shippingc2a.OrderDetailPortlet</param-value>
    </init-param>
         <load-on-startup>0</load-on-startup>
      </servlet>
      <servlet id="OrderMonthC2A">
         <servlet-name>Order Summary - C2A Version</servlet-name>
         <servlet-class>com.ibm.wps.pb.wrapper.PortletWrapper</servlet-class>
    <init-param>
             <param-name>c2a-application-portlet-class</param-name> 
             <param-value>com.ibm.wps.portlets.shippingc2a.OrderMonthPortlet</param-value>
    </init-param>
         <load-on-startup>0</load-on-startup>
      </servlet>

      <!-- rest of contents omitted  -->
      

You must modify portlet.xml to add a configuration parameter to each concrete portlet that exposes actions to the property broker through the WSDL file. The configuration parameter, c2a-action-descriptor, must specify a URL that points to the WSDL file that declares actions. The configuration parameter, c2a-nls-file, must specify the base name of an NLS resource file containing the translated text corresponding to the captions and descriptions of actions and properties. This file may be packaged in the same WAR file, so a relative path may be used.

If a portlet only encodes sources and does not specify any actions, you do not need to specify an action descriptor file. In this case, the properties associated with the sources should be programmatically registered for wiring to be possible. For an example, see the OrderMonthPortlet.java.

Sample portlet.xml file



<?xml version="1.0"?>
<!DOCTYPE portlet-app-def PUBLIC "-//IBM//DTD Portlet Application 1.1//EN" "portlet-1.1.dtd">
<portlet-app-def>
    <portlet-app uid="com.ibm.wps.portlets.shippingc2a">
    <portlet-app-name>Shipping Application Demo - C2A Version</portlet-app-name>
    <portlet id="OrderDetailC2A" href="WEB-INF/web.xml#OrderDetailC2A">
        <portlet-name>OrderDetailC2A</portlet-name>
        <cache>
         <expires>3600</expires>
      <shared>YES</shared>
     </cache>
     <allows>
            <maximized/>
            <minimized/>
        </allows>
        <supports>
            <markup name="html">
                <view output="fragment"/>
            </markup>
        </supports>
    </portlet>

    ...

    <concrete-portlet-app uid="concrete.com.ibm.wps.portlets.shippingc2a">
    <portlet-app-name>Shipping Application Demo - C2A Version</portlet-app-name>
    <concrete-portlet href="#OrderDetailC2A">
        <portlet-name>OrderDetailC2A</portlet-name>
        <default-locale>en</default-locale>
      <language locale="en">
         <title>Order Details</title>
         <title-short>OD</title-short>
         <description>Displays details for a particular order</description>
         <keywords>Property Broker, Click-to-Action, C2A, Cooperative Portlets</keywords>
      </language>
        <config-param>
           <param-name>c2a-action-descriptor</param-name>
           <param-value>/wsdl/OrderDetailC2A.wsdl</param-value>
        </config-param>
        <config-param>
           <param-name>c2a-nls-file</param-name>
           <param-value>nls.shippingc2a</param-value>
        </config-param>
    </concrete-portlet>

    ...

    </concrete-portlet-app>
</portlet-app-def>


 

WAR file considerations

Once the code and deployment changes have been made for using the property broker, additional libraries and files must be packaged along with the application. After you package the WAR file, it is ready to be installed. Use the following table to package the files in the correct location.

File name Path Original location
pbportlet.jar /WEB-INF/lib wp_root/pb/lib
WSDL file relative to the root of the WAR file or an absolute URL  

See also