Geertjan is a DZone Zone Leader and has posted 457 posts at DZone. You can read more from them at their website. View Full User Profile

How to Create a Web Service Client with Groovy and NetBeans Platform 6.8

12.14.2009
| 47394 views |
  • submit to reddit

Using NetBeans Platform Swing Components

In this section, we add a Swing "JTree" to the application. However, this will be a special "JTree", one that will integrate better with the NetBeans Platform than the standard "JTree" is able to do. This "JTree" is the NetBeans API "BeanTreeView" class. At the end of this section, you will have a "BeanTreeView" that will list the speakers retrieved from the web service, while being synchronized with the Properties window, though no properties will be shown in the Properties window since, at that point, you will be using the "AbstractNode" instead of the "BeanNode" with which this section will begin.

The "BeanTreeView" is more powerful than the "JTree", and better suited to a modular application, because it includes an understanding of "context sensitivity", which is helpful in modular applications, where many different modules may be available at the same time. Knowing the current context (selection management) is crucial in modular scenarios... and the standard Swing "JTree" doesn't have this knowledge built into it.

Also, a "BeanTreeView" displays a hierarchy of "Nodes", which are NetBeans Platform objects for visualizing business objects. Typically, in standard Swing, you need to create a different model object for each type of Swing component you're working with (JTree, JTable, JList, etc), while the Node is a generic model object that works with any (NetBeans version) of these, such as the "BeanTreeView". So, you create your model once and display it anywhere.

These topics are illustrated in detail in the example code and texts that follows below.

  1. Switch to the TopComponent Design view, right-click in the Palette, choose Palette Manager | Add from JAR. Then browse to "org-openide-explorer.jar", which is in "platform11/modules" folder, within the NetBeans IDE installation directory. Click Next and choose "BeanTreeView" and complete the wizard. You should now see BeanTreeView in the Palette. Drag it from the Palette and drop it on the window, to the left of the text pane. Move the components around and make sure you end up with something like this:

  2. Right-click the "ShakesViewer" Libraries node, choose "Add Module Dependency", and then set dependencies on "Nodes API" and "Explorer & Property Sheet API".

  3. Run the application and make sure you see the following when it starts up:

  4. Next, in the source code of the "ShakesViewerTopComponent" class, change the class signature to implement "ExplorerManager.Provider":
    final class CustomerTopComponent extends TopComponent implements ExplorerManager.Provider

    You will need to override "getExplorerManager", to return the current "ExplorerManager":

    @Override
    public ExplorerManager getExplorerManager() {
    return em;
    }

    At the top of the class, declare and initialize the ExplorerManager:

    private static ExplorerManager em = new ExplorerManager();

    Note: Watch Top 10 NetBeans APIs for details on the above code, especially the screencast dealing with the Nodes API and the Explorer & Property Sheet API.

  5. Now let's define a business object, which we will create whenever a new "ShakesWsClient" object is found in the global "Lookup".
    public class QuoteBean {

    private String name;
    private String play;
    private String speech;

    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public String getPlay() {return play;}
    public void setPlay(String play) {this.play = play;}
    public String getSpeech() {return speech;}
    public void setSpeech(String speech) {this.speech = speech;}

    }

    Now we will create new instances of this business object whenever a new quotation is retrieved from our Shakespeare search and added to the global Lookup!

  6. It is at this point that we need to define a "ChildFactory" object, which is a NetBeans API class that will generate the nodes that will be displayed in the "BeanTreeView". So, create a new class named "QuoteChildFactory", which extends "ChildFactory", and define it as follows, while making sure you understand all the comments in the code below:
    class QuoteChildFactory extends ChildFactory<QuoteBean> implements LookupListener {

    private Result<ShakesWsClient> result;
    private List<ShakesWsClient> quotes;

    public QuoteChildFactory() {
    //Listen for ShakesWsClient objects in the global Lookup:
    result = Utilities.actionsGlobalContext().lookupResult(ShakesWsClient.class);
    result.addLookupListener(this);
    //Call once to 'resultChanged' to activate the listener:
    resultChanged(new LookupEvent(result));
    }

    @Override
    public void resultChanged(LookupEvent le) {
    //Create a new ArrayList:
    quotes = new ArrayList();
    //Put all the instances of ShakesWsClient found in the global Lookup into a collection:
    Collection<? extends ShakesWsClient> coll = result.allInstances();
    //Iterate through the collection of found ShakesWsClient objects:
    for (ShakesWsClient client : coll) {
    //Add all the ShakesWsClient objects in the global Lookup to the ArrayList:
    quotes.add(client);
    //The list of objects has changed and the Nodes should be updated,
    //causing 'createKeys' to be invoked immediately, because of the 'true':
    refresh(true);
    }

    }

    @Override
    //Whenever a QuoteBean is added to the "quoteBeanList" in this method,
    //a call to "createNodeForKey" is triggered, which will receive the QuoteBean:
    protected boolean createKeys(List<QuoteBean> quoteBeanList) {
    if (!quotes.isEmpty()) {
    for (ShakesWsClient client : quotes) {
    QuoteBean s = new QuoteBean();
    s.setName(client.getSpeaker());
    s.setPlay(client.getPlay());
    s.setSpeech(client.getWords());
    quoteBeanList.add(s);
    }
    }
    return true;
    }

    //Each time "createNodeForKey" is called,
    //which happens when a new QuoteBean is added to the quoteBeanList above,
    //a new "Node" is created (see below),
    //which wraps the business object and displays it in the explorer view:
    @Override
    protected Node createNodeForKey(QuoteBean quoteBean) {
    try {
    return new BeanNode(quoteBean);
    } catch (IntrospectionException ex) {
    Exceptions.printStackTrace(ex);
    return null;
    }
    }

    }
  7. In line 7 above, notice this comment: "Listen for ShakesWsClient objects in the global Lookup". So, the question to be addressed now is: "How do we add new ShakesWsClient objects to the global Lookup?"

    We do that in the "ShakesViewerTopComponent" class. Whenever a new ShakesWsClient is created, i.e., this happens when a new quotation is retrieved from the web service, we need to add it to the global Lookup. That enables other parts of the application to do something, where necessary, i.e., that gives a context to other parts of the application. In this case, the introduction of new ShakesWsClients in the global Lookup enables the "ChildFactory" class to create new nodes in the "BeanTreeView" because it has a "LookupListener" that is defined to listen to the presence of "ShakesWsClient" objects.

    So how is this done? In "ShakesViewerTopComponent", instantiate a new "InstanceContent":

    private InstanceContent content = new InstanceContent();

    Next, in the constructor of "ShakesViewerTopComponent", instantiate the "QuoteChildFactory" and also populate the "Lookup" (i.e., the context) of the "ShakesViewerTopComponent":

    //Let the ExplorerManager display new child nodes asynchronously (because of 'true')
    //in its explorer views (e.g., the BeanTreeView that is defined in this class):
    em.setRootContext(new AbstractNode(Children.create(new QuoteChildFactory(), true)));

    //Add the ExplorerManager and the ActionMap to the Lookup of the TopComponent,
    //together with the InstanceContent:
    associateLookup(new ProxyLookup(
    ExplorerUtils.createLookup(em, getActionMap()),
    new AbstractLookup(content)));

    Now that the global Lookup of the "ShakesViewerTopComponent" is populated with (line 8) synchronization code enabling the Properties window to interact with the explorer views and (line 9) the "InstanceContent", we're ready to add objects dynamically to the "InstanceContent" object. So, finally, add one line ("content.add(client)" in line 14 below) to the "actionPerformed" of the "findButton":

    private void findButtonActionPerformed(java.awt.event.ActionEvent evt) {                                           
    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    ProgressHandle p = ProgressHandleFactory.createHandle(
    "Fetching the Shakespeare quote for "
    + "'" + searchField.getText() + "'...");
    p.start();
    ShakesWsClient client = new ShakesWsClient();
    client.findQuote(searchField.getText());
    resultPane.setText(client.getWords());
    StatusDisplayer.getDefault().setStatusText("Play: "
    + client.getPlay());
    content.add(client);
    p.finish();
    }
    });
    t.start();
    }
  8. Run the application and you should see the following:

    Switch between two returned nodes in the "BeanTreeView" and notice that the Properties window is automatically updated, i.e., the two explorer view are synchronized. However, the "JTextPane" is not an explorer view, hence it is not automatically synchronized with the explorer views. (And now you see the power of the explorer views in action!) In the next step, you will learn how you can synchronize a standard Swing component with the explorer views in your application.

  9. To synchronize the "JTextPane" with the explorer views, we first need to detect when a node in the explorer view is selected (by mouse and keyboard). At that time, we need to get the current "QuoteBean" in the local "Lookup" of the "Node" and use that instance of the "QuoteBean" to populate the text field, text pane, and status bar.

    • To do this, we need to create our own "BeanTreeView", i.e., you need to create a class (an inner class in the "ShakesViewerTopComponent") that extends "BeanTreeView". Within that class, get the events (such as the "MouseClicked" below) called on the "JTree" within the "BeanTreeView". You can use the global variable "tree" for this purpose, as done below.

      Then get the "ExplorerManager" via the explorer view and identify the "QuoteBean" assigned to the local Lookup (i.e., the local context) of the selected Node and use that "QuoteBean" to populate the text field, text pane, and status bar:

      public class MyBTV extends BeanTreeView {

      public MyBTV() {

      tree.addMouseListener(new MouseAdapter() {

      @Override
      public void mouseClicked(MouseEvent e) {
      //Find the ExplorerManager for this explorer view:
      ExplorerManager mgr = ExplorerManager.find(MyBTV.this);
      //Get the QuoteBean in the Lookup of the selected Node:
      QuoteBean quoteBean = mgr.getSelectedNodes()[0].getLookup().lookup(QuoteBean.class);
      if (quoteBean != null) {
      //Update the UI with the current QuoteBean's data:
      resultPane.setText(quoteBean.getSpeech());
      StatusDisplayer.getDefault().setStatusText("Play: " + quoteBean.getPlay());
      }
      }

      });
      }

      }

      However, right now the Lookup of the Node does not contain a "QuoteBean". That's because you're using "BeanNode" to define the Node. "BeanNode" is the simplest of the Node classes. It is useful for quick prototyping but can't really be used for more complex scenarios. It does not, for example, let you define a context (i.e., a "Lookup"). Therefore, open the "QuoteChildFactory" and create a custom node, extending "AbstractNode", where you pass a new "Lookup" object, containing the current "QuoteBean" to the superclass:

      public class QuoteNode extends AbstractNode {

      protected QuoteNode(QuoteBean quoteBean) {
      super(Children.LEAF, Lookups.fixed(quoteBean));
      setDisplayName(quoteBean.getName());
      }

      }

      Now change "createNodeForKey" to the following (i.e., as shown below, comment out the "BeanNode" code and return the new "QuoteNode" that you defined above):

      //Each time "createNodeForKey" is called, a new "Node" is created,
      //which wraps the business object and displays it in the explorer view:
      @Override
      protected Node createNodeForKey(QuoteBean quoteBean) {
      return new QuoteNode(quoteBean);
      // try {
      // return new BeanNode(quoteBean);
      // } catch (IntrospectionException ex) {
      // Exceptions.printStackTrace(ex);
      // return null;
      // }

      }
    • Finally, make sure that your application is using "MyBTV" (defined above) rather than the default "BeanTreeView" that you worked with earlier. Open the "ShakesViewerTopComponent" in "Design" mode (i.e., in the Matisse GUI Builder), right-click the text pane, choose "Customize Code", change the first line as shown below, so that "MyBTV" is created when the "ShakesViewerTopComponent" is created, and then click OK:

      Open the blue block in the source editor and make sure that you see this, i.e., note the "beanTreeView1 = new MyBTV()" in the final line below:

      private void initComponents() {

      jLabel1 = new javax.swing.JLabel();
      searchField = new javax.swing.JTextField();
      jScrollPane1 = new javax.swing.JScrollPane();
      resultPane = new javax.swing.JTextPane();
      findButton = new javax.swing.JButton();
      beanTreeView1 = new MyBTV();
      ...
      ...
      ...

Run the application again and you will not experience any problems when selecting a different node in the "BeanTreeView", i.e., whenever you choose a new "Node", new content will be added to the standard Swing "JTextPane". (However, since you are now using "BeanNode" instead of "AbstractNode", you need to provide your own icons and properties, both of which are done automatically by "BeanNode", via the NetBeans Nodes API Tutorial.) Run the application again and you should see something like this: 

Congratulations, at this point you have used some of the most important NetBeans API classes. In the next (and final) section, we will add some additional code that will create a custom root node (by subclassing "AbstractNode"), as well as a "Delete" menu item on each of the child nodes. The latter will enable items in the list to be removed, which is typical functionality in this type of application.

Published at DZone with permission of its author, Geertjan Wielenga.

Comments

Cej Hah replied on Mon, 2009/12/14 - 2:40pm

Geeez, it's almost 2010, can't we get away from Big Web Services already?!  REST is your friend.  SOAP is your enemy.

Ning Sun replied on Wed, 2009/12/16 - 3:49am

Really nice article.

I have a question about the code when binding mouse listener to bean tree view. That is step 8 in "Using NetBeans Platform Swing Components", line 15. How you got the reference of resultPane in MyBTV? Is there any trick?

Thank u for your great article.

 

 

Sven Alfred replied on Thu, 2009/12/31 - 3:38pm

Great walkthrough. SOAP or not... doesnt matter.

Combining the power of the platform with the simplicity of Groovy. I will use this as recipe for my own platform experiments.

cheers

Sven

Jeff Schwartz replied on Fri, 2010/01/29 - 9:16am

@Geertjan,

Very groovy to see these examples in Groovy. IMO Groovy is the gem of the Java virtual machine. I find myself using it a lot. I fall back to Java when I can find a legitimate reason to, such as when addressing critical performance issues. Otherwise, Groovy just makes more sense to me.

A lot of the developers I speak with believe Oracle's purchase of Sun doesn't bode well for Netbeans. Nothing that I have read leads me to believe that Oracle intends to throw their 'official' support behind it. Maybe now is time to set Netbeans free from any single corporate sponsor and before Oracle kills it with neglect.

 Be well.

 Jeff @ http://jeff-schwartz.blogspot.com/

Mateo Gomez replied on Tue, 2012/04/17 - 12:30am

 You just made my life easier

 

 mexican dessert recipes

Matt Coleman replied on Tue, 2012/04/17 - 12:43am

 

 this makes want to get to know netbeans...great job you guys

graphic design buffalo

Eugenios Ampelius replied on Mon, 2013/09/30 - 10:49am

 Groovy does a great job. What's the deal with REST, though? I haven't used it. Is it really better than SOAP? Thank you.

Cata Nic replied on Mon, 2013/09/02 - 4:10am

 The platform can be extended to something custom and dedicated do the client's needs ?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.