oktober 13, 2010

The one with the cookies and some action and a little bit of Lookup

So.. this is a blog post about cookies. Don't we all love cookies? Sure we do but this isn't about those small sugar bombs, neither is it about cookies in web browsers. Since this is a blog about Netbeans you might have already guessed that there is something in Netbeans RCP called Cookies.

I recently needed a way to tell my application that my data was updated and I knew that those cookies could help me. What I wanted to achieve was that by pressing a button in my toolbar the nodes that was updated had to be processed. What cookies does is giving your nodes a temporary state so you can track them.

Lets start by see if we can feed our nodes with some cookies...

First we need to create a cookie. In this example I'm creating an interface that extends a Node cookie. The interface contains one method called update().

public interface UpdateCookie extends Node.Cookie{
    public String update();
}

Ok.. now I have a cookie! What should I do with it? Should I have my node to implement this interface? No.. remember what I said earlier.. I want to give my node a temporary state. But add and remove interfaces can't be done at runtime can they? No.. so we need to something else in our node so lets look at that.


public class CustomerNode extends BeanNode<Customer> {
    public CustomerNode(Customer bean) throws IntrospectionException {
        super(bean);
        setDisplayName(bean.getName());
    }
}

So this is a basic BeanNode. It has something called a CookieSet which is a Java Collection with Cookies. By default it's empty but let's add our cookie to it. And remember it's an interface so it will ask us to implement all our abstract methods.

getCookieSet().add(new UpdateCookie() {
    @Override
    public String update() {
        return "Hello from" + getDisplayName() + " I'm updated!";
    }
});

Cookie added! And our update method will just return a String in this example but this is where you should call your application logic and do something with your data for example update a database or call a webservice..

This is a bit of bad example because now we have added the cookie to all our nodes. Normally you would have a method in your node that you call when the data is updated for example.. and that method should add the cookie to the cookieset. And to improve this example let's add a small if statement that only adds this cookie to one of the nodes.. like this:

if (bean.getName().equals(&quot;Apple&quot;)) {
    getCookieSet().add(new UpdateCookie() {.........  like above....}
}

As I said, this is only to add the cookie for just one node in this example, normally it's done somewhere else in your application.

Ok.. so now one of our Nodes has a UpdateCookie assigned. What can we do now? Let's create an action so we can do something with our nodes. What actions are is out of scope of this post but the basic is that an action is something that are called when you click an icon in the toolbar or select something in the menu. So let's create a menu item.

Add a new action by right click at your project and select New->Netbeans Module->Action. Leave the action to be Always enabled. Put it in the file category and call it UpdateCustomerAction with display name Update customers. Netbeans will now do everything for you and open up our newly created action. When an action is choosen the actionPerformed() method is called so this is where we will add our code.

What do we want to do? We want to grab all our Nodes that have the UpdateCookie assigned. First of all we need to get hold of our Nodes which are located in an Explorermanager in a Topcomponent. It's VERY important that we add this command to our Topcomponent first:

associateLookup(ExplorerUtils.createLookup(myExplorerManager, this.getActionMap()));

This tells the Topcomponent that it should place the current selected nodes into the Lookup of the Topcomponent. So each time one or serveral nodes are selected those nodes are put into the Lookup of our Topcomponent so we can grab them externally.

So lets go back to our action. Now we can get our nodes.. at first this code might be tempting to write:

public final class UpdateCustomerAction implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        Node[] nodes = TopComponent.getRegistry().getCurrentNodes();

        for (int i = 0; i &lt; nodes.length; i++) {
            Node node = nodes[i];
            UpdateCookie cookie = node.getCookie(UpdateCookie.class);
            if (cookie != null) {
                System.out.println(cookie.update());
            }
        }
    }
}

If we run our application now and select Update customer in the File menu with all our nodes selected in the Topcomponent it would correctly say in the Output window "Hello from Apple I'm updated!" but before explaining what happend I want to make the code above a bit cleaner. Instead of looping over all nodes and assign the cookie to a local variable and check for null there is a much more elegant way to do this. How? By using Lookup off course!

public void actionPerformed(ActionEvent e) {
        Collection&lt;? extends UpdateCookie&gt; lookupAll = TopComponent.getRegistry().getActivated().getLookup().lookupAll(UpdateCookie.class);
        for (UpdateCookie updateCookie : lookupAll) {
            System.out.println(updateCookie.update());
        }
    }
}

So what am I doing here? I'm asking for the current activated TopComponent. From this TopComponent I'm asking for the Lookup and tells it that I want to get all Nodes that are associated with an UpdateCookie. I will get a Java Collection with the result and since I just asked for UpdateCookie I only got the Cooke instance and not the whole Node and I can also be sure that it's safe to call the update method.

Decoupling
There is actually a third way to grab our cookies. It's a bit like above but using the global lookup instead. This is a proxy of all TopComponent lookups and from here you can get the selected node without having to reference to a specific TopComponet. This is good for decoupling and reusage. If we do it that way we can actually reuse our UpdateCustomerAction in other components. So the final code would look like this:

public void actionPerformed(ActionEvent e) {
        for (UpdateCookie updateCookie : Utilities.actionsGlobalContext().lookupAll(UpdateCookie.class)) {
            System.out.println(updateCookie.update());
        }
    }

So to summarize what have been done.

  • I added an UpdateCookie to my Apple node. UpdateCookie is an interface with one method. This method will be called and from it I can do whatever I need to do with my node. In this case I just returned a string with the display name of the Node.
  • Then I added an action that grabbed all my UpdateCookie nodes. I looped over them and called the update() method that returned me the string with the name of the Node.

After we have done our updates we want our Cookie to be removed from our node since it's only a temporary state. This can be done with this line added to the update() method:


getCookieSet().remove(getCookieSet().getCookie(UpdateCookie.class));

The next time you select Update customer nothing will happen because the cookie was removed.

Ending and exercise

This is just one example of what can be done with cookies. For fun and exercise try add SaveCookie to one of your nodes cookieset. Then watch the default Save button in the toolbar when you select different nodes. I will explain what happens in a later episode of Layer.xml :-)