2007-12-18

Central Lookup: Creating a central repository and lookup for an application context in a NetBeans RCP application

I have been working on a NetBeans RCP project where I needed a central place to register different interfaces and allow different views to operate on these regular POJOs one would use in a regular Swing application and to be able to listen to changes to them from the standpoint of some kind of an Application Context where different instances will come and go. A good example is a desktop application requiring a login. You will have user information and want to be able to track a global login or validation token.

I create a simple class and API in a NetBeans module and called it CentralLookup. This is all the code for such a simple thing (please ignore the formatting of the code...maybe I can update it later, but it seems blogger isn't wanting to format it correctly when posted):


package org.netbeans.modules.centrallookup.api;

import java.util.Collection;

import org.openide.util.Lookup.Result;

import org.openide.util.lookup.AbstractLookup;

import org.openide.util.lookup.InstanceContent;

/**
* Class used to house anything one might want to store

* in a central lookup which can affect anything within

* the application. It can be thought of as a central context

* where any application data may be stored and watched.

*

* A singleton instance is created using @see getDefault().

* This class is as thread safe as Lookup. Lookup appears to be safe.

* @author Wade Chandler

* @version 1.0

*/

public class CentralLookup extends AbstractLookup {

private InstanceContent content = null;

private static CentralLookup def = new CentralLookup();

public CentralLookup(InstanceContent content) {
super(content);
this.content = content;
}

public CentralLookup() {

this(new InstanceContent());
}

public void add(Object instance) {

content.add(instance);
}

public void remove(Object instance) {

content.remove(instance);
}

public static CentralLookup getDefault(){
return def;
}
}


The nice thing about this code is that it is real simple. The strange thing for me is that there hasn't been something already implemented in the base RCP. It seems pretty common a thing. For instance, the normal global lookups deal with Actions, Services, and Nodes (which are part of the NetBeans data model), and that is fine until one needs to work with generic interfaces and classes from a global perspective.


Regardless, I can now write code to listen to changes in this global context or Lookup in this case. The code doesn't have to know about any implementation except for the code which does the injection, and the code in other places can just setup a result and a listener. The following illustrates this a bit, and of course it is just a sub-set of the code:

final class CentralLookupTest1TopComponent extends TopComponent {

private static CentralLookupTest1TopComponent instance;
private Lookup.Result userInfoResult = null;
private CentralLookupTest1TopComponent() {

Lookup.Template template = new Lookup.Template(UserInformation.class);

CentralLookup cl = CentralLookup.getDefault();
userInfoResult = cl.lookup(template);
userInfoResult.addLookupListener(new UserInformationListener());
}

private javax.swing.JTextField name;
private javax.swing.JTextField pwd;
private javax.swing.JTextField token;
private javax.swing.JTextField uid;

private class SetterRunnable implements Runnable {
UserInformation ui = null;

public SetterRunnable(UserInformation ui) {
this.ui = ui;
}

public void run() {
name.setText(ui.getName());
pwd.setText(ui.getPassword());
uid.setText(ui.getUserID());
token.setText(ui.getToken());
}
}

private class UserInformationListener implements LookupListener {

public void resultChanged(LookupEvent evt) {
Object o = evt.getSource();
if (o != null) {
Lookup.Result r = (Lookup.Result) o;
Collection infos = r.allInstances();
if (infos.isEmpty()) {
EventQueue.invokeLater(new SetterRunnable(new DefaultUserInformation()));
} else {
Iterator it = infos.iterator();
while (it.hasNext()) {
UserInformation info = it.next();
EventQueue.invokeLater(new SetterRunnable(info));
}
}
}
}
}
}


Notice we have a result. The result has attached to it a listener. The listener knows about DefaultUserInformation and the UserInformation interface, and that is it. It then knows it needs to listen for instances of UserInformation and act accordingly. It doesn't know how it got there, but knows what it must do with it. We then have some other code in another class which will inject the instances into the CentralLookup based on some user input removing any previous:

private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
//ok we need to remove any user information from the central lookup
//and then we need to add this new one

CentralLookup cl = CentralLookup.getDefault();
Collection infos = cl.lookupAll(UserInformation.class);

if(!infos.isEmpty()){
Iterator it = infos.iterator();
while(it.hasNext()){
UserInformation info = it.next();
cl.remove(info);
}
}

DefaultUserInformation info = new DefaultUserInformation();
info.setName(name.getText());
info.setPassword(pwd.getText());
info.setUserID(uid.getText());
info.setToken(token.getText());
cl.add(info);

}


You can get a good idea from here the possibilities. This is a rather simple example, but it certainly shows it working. This can be used for any other services which need to use regular POJOs and not much else. Why add more overhead of class wrappers etc when they are not needed for everything? Sometimes one just needs a dynamic application context which can breakup the application into a more modular framework of simple classes and interfaces without major restrictions.

I have an AVI video formatted video of this in action. It is a very simple example, but it shows it working. The one piece of code doesn't know anything other than the fact that user information will appear magically in the CentralLookup in the form of the UserInformation interface.