Custom Lookup Provider For NetBeans Platform CRUD Tutorial
- by Geertjan
For a long time I've been planning to rewrite the second part of the NetBeans Platform CRUD Application Tutorial to integrate the loosely coupled capabilities introduced in a seperate series of articles based on articles by Antonio Vieiro (a great series, by the way). Nothing like getting into the Lookup stuff right from the get go (rather than as an afterthought)!
The question, of course, is how to integrate the loosely coupled capabilities in a logical way within that tutorial. Today I worked through the tutorial from scratch, up until the point where the prototype is completed, i.e., there's a JTextArea displaying data pulled from a database. That brought me to the place where I needed to be. In fact, as soon as the prototype is completed, i.e., the database connection has been shown to work, the whole story about Lookup.Provider and InstanceContent should be introduced, so that all the subsequent sections, i.e., everything within "Integrating CRUD Functionality" will be done by adding new capabilities to the Lookup.Provider.
However, before I perform open heart surgery on that tutorial, I'd like to run the scenario by all those reading this blog who understand what I'm trying to do! (I.e., probably anyone who has read this far into this blog entry.)
So, this is what I propose should happen and in this order:
Point out the fact that right now the database access code is found directly within our TopComponent. Not good. Because you're mixing view code with data code and, ideally, the developers creating the user interface wouldn't need to know anything about the data access layer. Better to separate out the data access code into a separate class, within the CustomerLibrary module, i.e., far away from the module providing the user interface, with this content:
public class CustomerDataAccess {
public List<Customer> getAllCustomers() {
return Persistence.createEntityManagerFactory("CustomerLibraryPU").
createEntityManager().createNamedQuery("Customer.findAll").getResultList();
}
}
Point out the fact that there is a concept of "Lookup" (which readers of the tutorial should know about since they should have followed the NetBeans Platform Quick Start), which is a registry into which objects can be published and to which other objects can be listening. In the same way as a TopComponent provides a Lookup, as demonstrated in the NetBeans Platform Quick Start, your own object can also provide a Lookup. So, therefore, let's provide a Lookup for Customer objects.
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
public class CustomerLookupProvider implements Lookup.Provider {
private Lookup lookup;
private InstanceContent instanceContent;
public CustomerLookupProvider() {
// Create an InstanceContent to hold capabilities...
instanceContent = new InstanceContent();
// Create an AbstractLookup to expose the InstanceContent...
lookup = new AbstractLookup(instanceContent);
// Add a "Read" capability to the Lookup of the provider:
//...to come...
// Add a "Update" capability to the Lookup of the provider:
//...to come...
// Add a "Create" capability to the Lookup of the provider:
//...to come...
// Add a "Delete" capability to the Lookup of the provider:
//...to come...
}
@Override
public Lookup getLookup() {
return lookup;
}
}
Point out the fact that, in the same way as we can publish an object into the Lookup of a TopComponent, we can now also publish an object into the Lookup of our CustomerLookupProvider. Instead of publishing a String, as in the NetBeans Platform Quick Start, we'll publish an instance of our own type. And here is the type:
public interface ReadCapability {
public void read() throws Exception;
}
And here is an implementation of our type added to our Lookup:
public class CustomerLookupProvider implements Lookup.Provider {
private Set<Customer> customerSet;
private Lookup lookup;
private InstanceContent instanceContent;
public CustomerLookupProvider() {
customerSet = new HashSet<Customer>();
// Create an InstanceContent to hold capabilities...
instanceContent = new InstanceContent();
// Create an AbstractLookup to expose the InstanceContent...
lookup = new AbstractLookup(instanceContent);
// Add a "Read" capability to the Lookup of the provider:
instanceContent.add(new ReadCapability() {
@Override
public void read() throws Exception {
ProgressHandle handle = ProgressHandleFactory.createHandle("Loading...");
handle.start();
customerSet.addAll(new CustomerDataAccess().getAllCustomers());
handle.finish();
}
});
// Add a "Update" capability to the Lookup of the provider:
//...to come...
// Add a "Create" capability to the Lookup of the provider:
//...to come...
// Add a "Delete" capability to the Lookup of the provider:
//...to come...
}
@Override
public Lookup getLookup() {
return lookup;
}
public Set<Customer> getCustomers() {
return customerSet;
}
}
Point out that we can now create a new instance of our Lookup (in some other module, so long as it has a dependency on the module providing the CustomerLookupProvider and the ReadCapability), retrieve the ReadCapability, and then do something with the customers that are returned, here in the rewritten constructor of the TopComponent, without needing to know anything about how the database access is actually achieved since that is hidden in the implementation of our type, above:
public CustomerViewerTopComponent() {
initComponents();
setName(Bundle.CTL_CustomerViewerTopComponent());
setToolTipText(Bundle.HINT_CustomerViewerTopComponent());
// EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
// Query query = entityManager.createNamedQuery("Customer.findAll");
// List<Customer> resultList = query.getResultList();
// for (Customer c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
// }
CustomerLookupProvider lookup = new CustomerLookupProvider();
ReadCapability rc = lookup.getLookup().lookup(ReadCapability.class);
try {
rc.read();
for (Customer c : lookup.getCustomers()) {
jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
}
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
Does the above make as much sense to others as it does to me, including the naming of the classes? Feedback would be appreciated! Then I'll integrate into the tutorial and do the same for the other sections, i.e., "Create", "Update", and "Delete". (By the way, of course, the tutorial ends up showing that, rather than using a JTextArea to display data, you can use Nodes and explorer views to do so.)