X

Geertjan's Blog

  • May 27, 2011

Data Drilling on the NetBeans Platform

Geertjan Wielenga
Product Manager

Let's say the business requirement is that you need to create an app that drills down into a set of data. We'll simulate the scenario via Yahoo News. Using tools in the IDE, you can very easily create a new window in a TopComponent that interrogates Yahoo News via a web service. Assuming that's been set up, let's drill from one news item into another news item. Rather arbitrarily, we'll assume that the children of the current node should be determined by the last word in the current title received from Yahoo:

Using the tools in the IDE, about which I can blog some other time, we can have a Search method like this, generated in the IDE, with all the related classes also being generated in the IDE:

private ResultSet searchYahoo(String searchString) {
try {
String query = searchString;
String type = "all";
java.lang.Integer results = 10;
java.lang.Integer start = 1;
String sort = "rank";
String language = null;
String output = "xml";
String callback = null;
RestResponse result = YahooNewsSearchService.search(query, type, results, start, sort, language, output, callback);
if (result.getDataAsObject(yahoo.newssearchservice.newssearchresponse.ResultSet.class) instanceof yahoo.newssearchservice.newssearchresponse.ResultSet) {
return result.getDataAsObject(yahoo.newssearchservice.newssearchresponse.ResultSet.class);
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}

OK, we now have a facility for searching Yahoo. We add the InstanceContent to the Lookup of the TopComponent via the established patterns. Then we add the ResultSet retrieved via Yahoo to the InstanceContent when the button is clicked:

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
ic.add(searchYahoo(jTextField1.getText()));
}

In our constructor, we have created our node hierarchy:

em.setRootContext(
new AbstractNode(Children.create(new YahooNewsChildFactory(), true)));

In the YahooNewChildFactory, we're listening to the selected window, which is the current window, for the new ResultSet (made available whenever the button is clicked) and then refreshing:

private class YahooNewsChildFactory extends ChildFactory<ResultType> implements LookupListener {
Collection<? extends ResultSet> resultSets;
Result<ResultSet> resultSetResult;
public YahooNewsChildFactory() {
resultSetResult = Utilities.actionsGlobalContext().lookupResult(ResultSet.class);
resultSetResult.addLookupListener(this);
}
@Override
protected boolean createKeys(List<ResultType> list) {
if (resultSets != null) {
ResultSet next = resultSets.iterator().next();
List<ResultType> result1 = next.getResult();
for (ResultType resultType : result1) {
list.add(resultType);
}
}
return true;
}
@Override
protected Node createNodeForKey(ResultType key) {
AbstractNode node = null;
node = new AbstractNode(Children.create(new GenericNextLevel(key.getTitle()), true), Lookups.singleton(key));
node.setDisplayName(key.getTitle());
return node;
}
@Override
public void resultChanged(LookupEvent le) {
Collection<? extends ResultSet> currentResultSets = resultSetResult.allInstances();
resultSets = currentResultSets;
refresh(true);
}
}

And then we have a GenericNextLevel which continually recreates itself based on the current search string:

private class GenericNextLevel extends ChildFactory<ResultType> {
private final String toSearch;
public GenericNextLevel(String toSearch) {
this.toSearch = toSearch;
}
@Override
protected boolean createKeys(List<ResultType> list) {
String[] allwords = toSearch.split(" ");
String last = allwords[allwords.length - 1];
ResultSet searchYahoo = searchYahoo(last);
List<ResultType> result1 = searchYahoo.getResult();
for (ResultType resultType : result1) {
list.add(resultType);
}
return true;
}
@Override
protected Node createNodeForKey(ResultType key) {
AbstractNode nextLevelNode = new AbstractNode(Children.create(new GenericNextLevel(key.getTitle()), true));
nextLevelNode.setDisplayName(key.getTitle());
return nextLevelNode;
}
}

Nice solution, I think.


Join the discussion

Comments ( 4 )
  • guest Friday, May 27, 2011
    Heya, Geertjan!
    This question isn't related to this post but I asked it on the NB forum sometime ago and is as of yet unanswered. I'm hoping you could provide this info:
    http://forums.netbeans.org/topic39391.html
    Post NB 7.0, are there any new APIs apart from the ones you listed in your top-10 APIs blog post, that can now be used outside the NB platform?
    Thanks a lot!
  • Jesse Glick Friday, May 27, 2011
    The YahooNewsChildFactory constructor may be a memory leak. (Not sure if a Lookup will hold references to its Result's.) You should rather use WeakListeners; or, extend ChildFactory.Detachable, and add/remove the listener in add/removeNotify.
    There is no need to store a resultSets collection and check for it being null. Anyway this will show no child nodes until a lookup event is fired. So a complete class should be something like:
    class YahooNewsChildFactory extends ChildFactory.Detachable implements LookupListener {
    Lookup.Result<ResultSet> resultSetResult;
    protected void addNotify() {
    resultSetResult = ...;
    resultSetResult.addLookupListener(this);
    }
    protected void removeNotify() {
    resultSetResult.removeLookupListener(this);
    resultSetResult = null;
    }
    public void resultChanged(LookupEvent le) {
    refresh(true);
    }
    protected boolean createKeys(List<ResultType> list) {
    for (ResultSet rs : resultSetResult.allInstances()) {
    list.addAll(rs.getResult());
    }
    return true;
    }
    // createNodeForKey...
    }
    Also I guess searchYahoo should not be called from EQ, i.e. from jButton1ActionPerformed. You could run the body of jButton1ActionPerformed asynch, but then there would be no GUI indication that a search was in progress. Better I think to get rid of the Lookup with its ResultSet, which serves no real purpose here (both the button and the tree are in the same GUI component); simpler to directly hold a reference to the YahooNewsChildFactory from the TopComponent, so that when the button is clicked, you can show some kind of progress GUI which is replaced by the actual list when the result comes back - I am assuming the web service interface offers no incremental results, it just blocks for a while and then produces a list of hits.
    (It should be OK for GenericNextLevel.createKeys to call searchYahoo synchronously, since GNL is created as an asynch children list so a "Please wait..." node will be shown while it is in progress. It is pointless - though harmless - for YahooNewsChildFactory as written to be asynch, since it does no significant work.)
  • Geertjan Friday, May 27, 2011
    Hi Jesse, Thanks for the comments. Hi guest, I don't really understand the question but I guess the answer is No.
  • Database Consultant Friday, May 27, 2011
    Thanks for this data drilling sample. It is gives us ideas to solve something similar we are faced with.
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.