ShowTable of Contents
Introduction
The IBM® Lotus® Notes® 8.5.x Standard client is based on the IBM Lotus Expeditor 6.x platform, and ISVs, BPs, and plug-in developers have leveraged this extensible platform to come up with a set of relevant plug-in features.
Based on our experience working with such developers and going through the issues they faced, here we present some best practices for the Notes client's plug-in development as well as some related tips that have proven useful. Even though this information might exist in scattered bits on the Web, we've attempted to compile it in this single article to serve as a one-stop resource.
Deciding how to code plug-ins
“Our plug-in needs to start very early during platform's startup.” Developers often have this requirement for their plug-in(s) under development, but instead they should decide how to code it based on the answers to the following questions:
a) Does the plug-in really need to start so early?
b) What if the plug-in is lazily loaded?
c) What if the plug-in is loaded after the client window opens?
d) Should the plug-in be automatically started only when the Notes client window (not IBM Lotus Domino® Designer) or Domino Designer (not Notes client) is first launched?
Below we explain a few ways to start your bundles automatically:
Extending Eclipse-defined org.eclipse.ui.startup extension point
For more information on this topic, refer to the Eclipse documentation topic, "
Startup."
This extension point is used to register the plug-ins that you want to be activated at workbench startup. This is tied to the workbench, so even if you have more than one application built on the same base/foundation, the plug-in will load irrespective of the application for which the workbench is starting.
You should minimize the number of such plug-ins as these can impact the startup performance of the Eclipse-based application. The preference should always be to have plug-ins load lazily as that will optimally use the system resources and yield better performance.
The code snippet in listing 1 shows how to register your startup class. Note that Eclipse recommends that the main activator/plug-in class
not be the startup class. The startup classes are loaded on background threads; thus, the earlyStartup() cannot directly do the UI operations unless you use UIJob or Display object.
Listing 1. org.eclipse.ui.startup extension point example
<extension
point="org.eclipse.ui.startup">
<startup
class="com.ibm.ndemo.examples.MyStartup">
</startup>
</extension>
public class MyStartup implements IStartup {
/* (non-Javadoc)
* @see org.eclipse.ui.IStartup#earlyStartup()
*/
public void earlyStartup() {
System.out.println("The startup class for demo plug-in is loaded");
}
}
Extending Expeditor-defined com.ibm.rcp.personality.framework.PersonalityStartup extension point
For more information on this topic, refer to the Lotus Expeditor (XPD) documentation topic, "
Personality Startup."
This uses the same IStartup interface and is similar to the org.eclipse.ui.startup extension point, except that it activates the given plug-in only when the specified personality is launched (see listing 1).
This extension point is an answer to our question “d” above, that is, if you specify the Notes client's personality, the plug-in will be activated only if the Notes personality's launch starts the workbench and not if Domino Designer is triggering the workbench's start.
This is useful if your plug-in wants to avail the product's (Lotus Notes, Domino Designer, or IBM Lotus Symphony
TM) services that would be available upon the personality's launch.
Listing 2. com.ibm.rcp.personality.framework.PersonalityStartup extension point example
<extension point="com.ibm.rcp.personality.framework.personalityStartup">
<personalityStartup
class="com.ibm.ndemo.examples.MyStartup"
id="com.ibm.ndemo.examples.myStartup">
</personalityStartup>
</extension>
<extension
point="com.ibm.rcp.personality.framework.personalityExtensions">
<personalityExtension
targetID="com.ibm.rcp.platform.personality">
</personalityExtension>
</extension>
Opting for the "startLevels" Lotus Expeditor method
For more details, refer to the XPD product documentation topic, “
Contributing plug-ins to the platform life cycle.”
The XPD rich client platform provides a couple of ways to start your plug-ins early during the platform start. You can choose either to deploy a new fragment to the "com.ibm.rcp.platform.lifecycle.platform" host or use System properties. The XPD documentation for these methods is available in the topic, “
Contributing plug-ins to the platform life cycle”.
Though this could make sense for cases in which you are building your own product on top of the Expeditor framework,
we do not recommend it and discourage its custom use for the established products (Lotus Notes, Symphony, Domino Designer, etc.)
These products have their own startup sequence by which their list of particular services could be made available for other plug-ins' use. Opting for the above custom plug-in contribution to the platform life cycle could interfere with the products' default startup sequence.
In addition, if the custom plug-ins directly or indirectly attempt to access the services that these products would register, there may be issues with respect to services' availability. If you have no way other than using this method, be aware of the likely consequences with respect to availability of platform's services.
Summary
Of the above described methods, only the third option (Section 2.3) is not recommended. As such, for any Eclipse-based product, the extenders should try their best for no startup plug-ins and have their plug-ins load lazily, to ensure no performance impact to the product or any unwanted side effects.
Background and UI operations using threads in Eclipse
For more details refer to the "
On the Job: The Eclipse Jobs API" topic.
Using plain Java
TM threads, you can accomplish both background and UI operations; however, the recommendation is to use the Eclipse Jobs APIs. The Job Manager framework of Eclipse has abstracted lots of useful methods around threading that is otherwise not available in plain Java thread APIs:
- Use "org.eclipse.core.runtime.jobs.Job" instead of java.lang.Thread wherever it makes sense, particularly for doing some background operations.
- Use "org.eclipse.ui.progress.UIJob" for the asynchronous UI updates.
For the sake of simplicity, here we refer to "org.eclipse.core.runtime.jobs.Job" as a background Job and "org.eclipse.ui.progress.UIJob" as a UIJob. Also:
- The Jobs framework passes a progress monitor to each Job & UIJob instance and, using this progress monitor, the Job instance can display the Job progress through the UI. If you wish to show your Job's progress in the UI, use setSystem(false) else setSystem(true) API before scheduling your Job.
- For the Jobs that display progress in the UI, the Job name displays in the UI, so make sure that it is meaningful and for multi-lingual support, if it is to be translated.
- Instead of Display.getDefault(), either use "Display.getCurrent()" or "PlatformUI.getWorkbench().getDisplay()".
- For asynchronous UI operations the best option is to use UIJob. The Display class offers the syncExec() API to do the UI activity synchronously, but use this cautiously to not cause any hang situations.
- Use IJobChangeListener to know when your Job finishes and if you wish to take further action on that event.
- The Jobs API also lets you specify Job scheduling rules. These rules help to serialize your Job's scheduling/execution as well as limit the number of Job instances on execution at any given time.
- If you define a family of Jobs and then wish to cancel not just one but all the Job instances belonging to that family, you can use JobManager.getInstance().cancel(Object family) API.
Contributing to the Notes Open launcher
For more information, refer to “
Contributing bookmarks to the launcher.”
The Open launcher in the Notes client is extensible (see figure 1), and you can contribute your entries to the launcher statically or programmatically. If you don't have genuine reasons to opt for dynamic contributions, the best option is the static contribution so that the plug-in code need not be loaded for UI display.
Figure 1. Open launcher
Static contribution
(1) com.ibm.rcp.ui.launcherSet
For more details, refer to the “
Launcher Extension Point” topic.
Using this, you can specify a static list of items that should display in the Open launcher menu. The corresponding action classes will be loaded only when you click on the respective items. The API provides the following default launcher item types:
- NativeProgramLauncherContributionItem: Use this if you want your custom native application to be launched at the click of this item.
- PerspectiveLauncherContributionItem: Use this if you have an Eclipse perspective that lays out a custom UI, for example, a dashboard of Sales of products or leave applications.
- UrlLauncherContributionItem: Use this if you want it to open a specific URL, similar to your favorite bookmarks in a Web browser.
Listing 3. com.ibm.rcp.ui.launcherSet extension point example
<extension
point="com.ibm.rcp.ui.launcherSet">
<LauncherSet
id="com.ibm.ndemo.examples.LauncherSet1"
label="MyDemoLauncherSet">
<urlLaunchItem
autoStart="false"
id="com.ibm.ndemo.examples.urlLaunchItem1"
label="IBM Website"
url="http://www.ibm.com">
</urlLaunchItem>
<nativeProgramLaunchItem
autoStart="false"
environmentMode="APPEND"
id="com.ibm.ndemo.examples.nativeProgramLaunchItem1"
label="Windows Notepad"
platform="win32"
programCommand="c:\windows\notepad.exe">
</nativeProgramLaunchItem>
</LauncherSet>
</extension>
(2) com.ibm.rcp.ui.bookmarkprovider
This extension lets you register your own bookmark provider with the launcher framework. Whenever the framework needs to know your contribution, the bookmark provider class will be called in for the appropriate method(s).
NOTE:
Do not use this for Notes client. There already exists a BookmarkProvider to provide Notes' bookmarks dynamically, and there can be only one per window.
Programmatic contribution
(1) com.ibm.rcp.ui.launcher.LauncherManager APIs let you add/remove launcher items at runtime (see listing 4). Since LauncherManager is a wrapper over jface's ContributionManager, you have access to all those methods. However, be sure to work with only your contributions so that others' contributions are not affected.
In addition, it gives you some predefined group markers, the preferred one being "additions". Of course, you can also define your own separator/group. You opt for this if the launcher entry's contribution is governed by some dynamic conditions such as a policy setting.
Listing 4. Code for dynamically adding contribution item using LauncherManager APIs
LauncherManager launchManager = LauncherManager.getInstance(window);
IContributionItem item = launchManager.find(LAUNCH_CONTRIB_ID);
if(item == null) {
//Add our launcher item only if one doesn't exist already
UrlLauncherContributionItem urlContribItem = new UrlLauncherContributionItem();
urlContribItem.setId(LAUNCH_CONTRIB_ID);
urlContribItem.setLabel("My UrlLauncher Entry"); //$NON-NLS-1$
urlContribItem.setUrl("http://www.ibm.com"); //$NON-NLS-1$
launchManager.appendToGroup(LauncherManager.GROUP_MARKER_CONTRIBUTIONS_END,urlContribItem);
launchManager.update(true);
}
else{
//Item is already there, no need to add again
}
(2) Listeners. If you add, be sure to remove them when not needed. Any API that lets you add listeners will also let you remove them. In the Eclipse world, be sure to follow the rule “if you add them, remove when done with listening.” If you don't, you're unnecessarily adding overhead on the listener notifying code to trigger your listener.
A majority of the listeners are triggered on the Eclipse UI/main thread, so be diligent about what you do in the listener callbacks. Any time-consuming operation in your listener's callback would result in slower performance for the original event.
(3) Notes Java API-based plug-in development. The Notes backend Java APIs have existed for quite some time, even before introduction of the Notes standard client. Since the Notes v8 release, the Eclipse plug-ins can also use the APIs by depending on the "com.ibm.notes.java.api" plug-in. Since these APIs are for back-end access, it is recommended to use them in the context of background Jobs.
If you're using your own threads or wish to use the static methods in Notes Java APIs, then use NotesThread.sInitThread() to initialize the thread and NotesThread.stermThread() to terminate the thread.
The Notes APIs throw exceptions, so it is recommended to have the thread termination done in the final block to ensure that it is always called.
These two methods go in a pair:
- NotesThread.sInitThread
- NotesThread.sTermThread
The call to sinitThread must have a matching stermThread without fail; otherwise, you're likely to see errors in the client and, at times, the Notes client could crash. To relieve programmers from the development errors of missing on the paired implementations of init/term thread calls, starting with Notes v8.5.1, a new class,
com.ibm.notes.java.api.util.NotesSessionJob, was introduced.
The NotesSessionJob deprecates NotesJob for better security and memory handling (see listing 5). This class abstracts the thread initialization and termination, NotesSession creation, and you must be sure to focus on the core backend API usage.
Listing 5. Using NotesSessionJob
public class MyNotesJob extends NotesSessionJob {
/**
* @param jobName
*/
public MyNotesJob(String jobName){
super(jobName);
}
@Override
protected IStatus runInNotesThread(Session arg0, IProgressMonitor arg1)
throws NotesException {
Database db = getSession().getDatabase("", "mydisc.nsf");
if(db==null)
return Status.CANCEL_STATUS;
View view = db.getView("All Documents");
if(view==null)
return Status.CANCEL_STATUS;
System.out.println("What is the view entry collection size: "+view.getEntryCount());
ViewEntryCollection collection = view.getAllEntries();
ViewEntry entry = collection.getFirstEntry();
System.out.println(entry.getNoteID());
while((entry=collection.getNextEntry())!=null)
{
System.out.println(entry.getNoteID());
}
return Status.OK_STATUS;
}
}
Conclusion
The lazy loading of plug-ins is the recommended option, but if that is not possible, we now know which are the best options for starting up plug-ins early. The Eclipse Jobs APIs give you a better handle for carrying out background and UI operations, and the tips in this article should help you develop custom plug-ins that avoid any interference with the product's sequence of events.
Tell us what you think
Please visit this link to take a one-question survey about this article:
Resources
Eclipse home page:
http://www.eclipse.org
developerWorks® article, “Extending the IBM Lotus Notes V8 sidebar and toolbar:”
http://www.ibm.com/developerworks/lotus/library/notes8-sidebar/
developerWorks article, "Integrating IBM Lotus Notes data into the Lotus Notes V8 sidebar and toolbar:"
http://www.ibm.com/developerworks/lotus/library/notes8-data/
IBM Redbooks® publication, "Leveraging Notes-specific APIs" chapter:
http://www-10.lotus.com/ldd/ddwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Redbooks%3A+Creating+Plugins+for+Lotus+Notes%2C+Sametime%2C+and+Symphony#action=openDocument&res_title=Leveraging_Notes_specific_APIs&content=pdcontent
Eclipse corner article, "On the Job: The Eclipse Jobs API:"
http://www.eclipse.org/articles/Article-Concurrency/jobs-api.html
developerWorks article, "Java access to the Domino Objects, Part 1:"
http://www.ibm.com/developerworks/lotus/library/ls-Java_access_pt1/index.html
developerWorks article, "Java access to the Domino Objects, Part 2:"
http://www.ibm.com/developerworks/lotus/library/ls-Java_access_2/index.htmlAbout the author
Jaitirth Shirole is an IBM Advisory Software Engineer working as a Notes client developer. He has been with IBM for 9+ years and has worked on Notes client development starting with the 8.0 release. His interests have been in Java programming, ranging from client- to server- side technologies. You can reach him at
jaitirth.shirole@in.ibm.com.