ShowTable of Contents
Introduction
The action in this tutorial is created for the browser container and can be used to save state for each container instance. The "state" is the history of the property values that are wired to each container instance, each of which will have its own state. We also explain how to deploy this action to IBM Lotus Notes and how to use the Composite Application Editor (CAE) to make a composite application with this action.
To get the most from this tutorial, you need to have Lotus Notes 8.5.1 and the Lotus Expeditor 6.2.1 toolkit installed into your Eclipse 3.4 Integrated Development Environment (IDE). Also, it's be best to complete the wiki tutorial, "
Creating custom actions in Java," prior to performing this one.
Creating the Eclipse plugin
a) Create a new Eclipse plugin project just as you did in Step 1 of "
Creating custom actions in Java." For this tutorial, the plugin should be named "com.ibm.ca.wiki.action.state".
b) Add the required bundles and imported packages for this plugin as shown in figure 1.
Figure 1. Dependencies window
c) Create a custom action by extending the com.ibm.rcp.composite.container.core.actionConfiguration extension-point. You can refer to Step 2 of Creating a custom action in Java. For this tutorial, the action is named AppendToQueue, and it's targeted for browser container. Figure 2 shows the configuration.
Figure 2. Extensions window
Creating the Action code
Now we create the Java code for our
AppendToQueue action, which is extended from the com.ibm.rcp.composite.container.core.actions.Action. We need to override the execute() method to do our work. The
AppendToQueue action code is shown in listing 1. It saves state for each container instance in an internal Map (field is named "state" in the code), and also updates the browser UI to show states of current instance when the action is executed.
Listing 1. AppendToQueue action code
package com.ibm.ca.wiki.action.state;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.swt.widgets.Display;
import com.ibm.rcp.browser.service.WebBrowser;
import com.ibm.rcp.composite.container.browser.BrowserAppContainer;
import com.ibm.rcp.composite.container.core.AppContainer;
import com.ibm.rcp.composite.container.core.actions.Action;
import com.ibm.rcp.composite.container.core.events.LandmarkEvent;
public class AppendToQueue extends Action
{
private final int MAX_HISTORY_SIZE = 10;
private Map state;
private String htmlRoot;
private BrowserAppContainer bc ;
private Object context;
private String viewId;
private String html;
private final static String CLAZZ_NAME = AppendToQueue.class.getName();
private final static Logger _logger = Logger.getLogger(CLAZZ_NAME);
public AppendToQueue()
{
super();
state = new HashMap();
Location location = Platform.getInstanceLocation();
String instanceAreaPath = location.getURL().getPath();
htmlRoot = instanceAreaPath;
}
@Override
public void execute(AppContainer container, LandmarkEvent event,Object context)
{
bc = (BrowserAppContainer)container;
//Need to replace the : in viewId , to avoid non-supported char in url
viewId = bc.getViewId().replace(':', '-');
this.context = context;
File f = new File( htmlRoot );
html = f.getAbsolutePath() + File.separatorChar + viewId + ".html";
String pValue = event.getPropertyValue();
String p = event.getProperty();
if(pValue != null)
saveAndPresentState( p, pValue.trim());
}
private void saveAndPresentState(String property, String value)
{
saveState(property, value);
generateStateHTML();
updateUI();
}
private void saveState(String property, String value)
{
_logger.info("Saving state. Property : " + property + " Value : " + value);
Map instanceState = (Map)state.get(viewId);
if(instanceState == null){
instanceState = new HashMap();
}
List propHistory = (List)instanceState.get(property);
if(propHistory == null){
propHistory = new LinkedList();
}
if(propHistory.size() == MAX_HISTORY_SIZE)
propHistory.remove(0);
propHistory.add(value);
instanceState.put(property, propHistory);
state.put(viewId, instanceState);
}
private void generateStateHTML()
{
final String _method = "generateStateHTML";
Map instanceState = (Map)state.get(viewId);
String content = HtmlHelper.generateStateTable(instanceState);
File file = new File(html);
if(file.exists())
file.delete();
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(content);
writer.flush();
} catch (IOException e) {
_logger.logp(Level.WARNING, CLAZZ_NAME, _method,"Fail to generate html file " + html, e);
}finally{
if(writer != null)
try {
writer.close();
} catch (IOException e) {
_logger.logp(Level.WARNING, CLAZZ_NAME, _method,"Fail to generate html file " + html, e);
}
}
}
private void updateUI()
{
final WebBrowser browser = bc.getWebBrowser(context);
Display display = browser.getDisplay();
display.asyncExec(new Runnable(){
public void run()
{
browser.setUrl(html);
}
});
}
}
In the AppendToQueue action, we use an HtmlHelper util class to help generate the html markup. The code of HtmlHelper is shown in listing 2.
Listing 2. HtmlHelper code
package com.ibm.ca.wiki.action.state;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HtmlHelper
{
private HtmlHelper()
{
}
public static String generateStateTable(Map state)
{
StringBuilder sb = new StringBuilder();
String header = "<html><body><table border=\"1\"><tr><th>Property</th><th>State</th></tr>";
sb.append(header);
Set properties = state.keySet();
for(Iterator it = properties.iterator(); it.hasNext(); ){
String property = (String)it.next();
List history = (List)state.get(property);
String row = generateRow(property, history);
sb.append(row);
}
String footer = "</table></body></html>";
sb.append(footer);
return sb.toString();
}
private static String generateRow(String property, List history)
{
StringBuilder sb = new StringBuilder();
String header = "<tr>";
sb.append(header);
sb.append("<td>" + property + "</td>");
StringBuilder historyString = new StringBuilder();
for(Iterator it = history.iterator(); it.hasNext();){
historyString.append((String)it.next() + "</br>");
}
sb.append("<td>" + historyString.toString() + "</td>");
String footer = "</tr>";
sb.append(footer);
return sb.toString();
}
}
Packaging and installing the AppendToQueue action
Follow Step 4 in the "Creating custom actions in Java" tutorial to create an Eclipse feature/update site, and deploy the AppendToQueue action to Lotus Notes. After restarting Lotus Notes, we will begin to create a composite application with the AppendToQueue action. Alternatively, you can use widgets to deploy the new update site to the Notes client.
Assembling a composite application with AppendToQueue action
Now let's create a new composite application to demonstrate our newly created custom action:
a) Create a new Composite Application NSF, using the menu options File -- Application -- New; you are presented with a dialog box to create a new application (see figure 3).
b) Make sure you select "Blank Composite Application" for the default template.
Figure 3. New Application window
The composite for this tutorial are two side-by-side Managed Browser components. The left-hand component is our regular "browser" in which we surf the Internet for interesting information. The browser on the right-hand side is where we call our AppendToQueue action for two properties.
c) In the left-hand browser, open the Advanced properties dialog by right-clicking on the component in the left-hand navigation tree. We will name the left-hand component "Main Browser" and the right-hand component "ReceiveBrowser" (see figure 4).
Figure 4. Two browser components
Configuring our main browser component
a) First, we must define what properties we want to publish from our left-hand component. Let's create two properties, one called "BaseballPlayersName" and the other "BaseballTeamsName" (see figure 5). We will publish these properties at two different landmarks.
Figure 5. Create properties
b) As you can imagine by their name, we will publish a baseball player's name when we navigate to a player profile, and we will publish a baseball team's name when we navigate to a team roster. Figure 6 shows the landmark definitions for these two kinds of pages.
Figure 6. Landmark definitions
Here is the text representation of the landmark values, so you can copy and paste:
*?player_id=*
*mlb.com/team/roster_active.jsp?c_id=*
Configuring the receive browser component
a) Now we configure the right-hand component to have our custom action called when the main browser publishes a property. Once again, in the Advanced properties section of the receive browser, we add two wireable properties, PlayersName and TeamName (see figure 7).
Figure 7. Add wireable properties
b) We now must define our landmarks and have our custom action called with the incoming property values. Figure 8 shows the Landmarks tab for our receive component.
Figure 8. Landmarks tab
As you can see, we merely map the two properties in the Data Change event to our
AppendToQueue action, meaning that any time these properties are wired and data comes in, our new action will be called.
Wiring our components
The last step in creating our application is wiring the two new components together. Since the property's names are almost the same, this should be easy. We prefer using the wiring dialog for this. Figure 9 shows the end result with the two new wires.
Figure 9. Create New Wire window
Using the newly created composite application
We are now ready to use our new application and see the custom action working:
a) Change the URL on the left-hand window and navigate throughout
MLB.com (see figure 10). The team name and players name states should get added to the previously visited players and teams. Here are some sample rosters and player URLs:
Yankees:
http://newyork.yankees.mlb.com/team/roster_active.jsp?c_id=nyy
Red Sox:
http://boston.redsox.mlb.com/team/roster_active.jsp?c_id=bos
Derek Jeter:
http://newyork.yankees.mlb.com/team/player.jsp?player_id=116539
Jason Veritek:
http://boston.redsox.mlb.com/team/player.jsp?player_id=123660
Figure 10. Navigate through MLB.com
Conclusion
This article adds a new example of custom actions and shows how to leverage the custom action in composite applications. The concept for this action is to show the ability for each container instance to keep its own state. From this article, you can see the custom action is quite useful and easy to extend.