Loosely Coupled Reloadable Capabilities for CRUD Applications
Antonio has an interesting series entitled NetBeans Platform on Standalone Swing Applications. I've been following especially the parts relating to the "reloadable" capabilities, since I think the area of "capabilities" is one of the most powerful yet least understood parts of the NetBeans APIs.
So, below I reproduce a small part of Antonio's Tweet scenario, but converted to a database scenario. I still have open questions about all of this, but here's a start for discussion anyway.
- Begin by creating a new NetBeans Platform application named "CustomerCRUD" or whatever else you'd like to call it..
- Generate a Customer entity (with a related Discount entity) via wizards in the IDE, as described in the first parts of the NetBeans CRUD Application Tutorial. Then you will have a JAR containing the Customer.class, Discount.class, and the persistence.xml file. Put that JAR, together with the database server client JAR (e.g., DerbyClient.jar) and the JPA-related JARs (e.g., EclipseLink JARs) into a library wrapper module (named "CustomerLibraries", for example), all of which is described in the tutorial above. You now have one module, a library wrapper module, that contains about 4 different JARs.
- Next, create a second module (named "CustomerAPI", for example), which will contain your API classes, together with the capabilities that should be made available to other modules. (Or maybe the capabilities should be in a separate "CustomerUtilities" module.) In this module, we will have four classes, all described in the next step.
- We start by creating capabilities that we are going to be reusing throughout our application in a loosely coupled way. First of all, we'll create a capability for reloading entities, followed by a capability for reloading our view:
public interface ReloadableQueryCapability {
public void reload() throws Exception;
}public interface ReloadableViewCapability {
public void reloadChildren() throws Exception;
}Next, we create a data access object that will access the database, via a SQL string passed into it:
import client.Customer;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
public final class CustomerSearchDAO {
public List<Customer> search(String search) {
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerPU").createEntityManager();
javax.persistence.Query query = entityManager.createQuery(search);
List<Customer> customers = new ArrayList<Customer>();
List<Customer> resultList = query.getResultList();
for (Customer c : resultList) {
customers.add(c);
}
return customers;
}
}And we finish our API module by creating a query object to which we'd like to assign the capability for it to be reloaded. Therefore, we need to implement the Lookup.Provider class and return a Lookup that contains an implementation of the ReloadableQueryCapability:
import client.Customer;
import java.util.ArrayList;
import java.util.List;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
public final class CustomerQuery implements Lookup.Provider {
private List<Customer> customers;
private Lookup lookup;
private InstanceContent instanceContent;
private String sqlstring;
public CustomerQuery() {
customers = new ArrayList<Customer>();
// Create an InstanceContent to hold abilities...
instanceContent = new InstanceContent();
// Create an AbstractLookup to expose InstanceContent contents...
lookup = new AbstractLookup(instanceContent);
// Add a "Reloadable" ability to this entity
instanceContent.add(new ReloadableQueryCapability() {
@Override
public void reload() throws Exception {
ProgressHandle handle = ProgressHandleFactory.createHandle("Loading...");
handle.start();
CustomerSearchDAO dao = new CustomerSearchDAO();
List<Customer> result = dao.search(sqlstring);
for (Customer customer : result) {
if (!getCustomers().contains(customer)) {
getCustomers().add(customer);
}
}
handle.finish();
}
});
}
public String getSqlstring() {
return sqlstring;
}
public void setSqlstring(String sqlstring) {
this.sqlstring = sqlstring;
}
@Override
public String toString() {
return sqlstring;
}
@Override
public Lookup getLookup() {
return lookup;
}
public List<Customer> getCustomers() {
return customers;
}
}Our API module is now complete. It contains database access code, together with reload capabilities, one of which we're already using in our query object to enable it to be reloaded.
- Create a third module ("CustomerViewer", for example), where everything described in this step will be created. First, we create a new Action that will receive a Lookup. In the Lookup we look for the ReloadableViewCapability and, if it is there, we call the implementation's "reloadChildren" method. What that does we don't know, that depends on the implementation we get from the Lookup that is passed in.
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.my.api.ReloadableViewCapability;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
public final class ReloadAction extends AbstractAction {
private ReloadableViewCapability reloadableViewCapability;
public ReloadAction(Lookup lookup) {
reloadableViewCapability = lookup.lookup(ReloadableViewCapability.class);
putValue(AbstractAction.NAME, "Reload");
}
@Override
public void actionPerformed(ActionEvent e) {
if (reloadableViewCapability != null) {
try {
reloadableViewCapability.reloadChildren();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
}
}Next, in the same module, we create a factory class which we will, later, attach to the root node of our view. The factory class runs the query, retrieved from the Lookup of the query, and now the call to "getCustomers" on the query contains the current state of the database.
import client.Customer;
import java.util.List;
import org.my.api.CustomerQuery;
import org.my.api.ReloadableQueryCapability;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
class RootNodeChildFactory extends ChildFactory<Customer> {
private CustomerQuery query;
public RootNodeChildFactory(CustomerQuery query) {
this.query = query;
}
@Override
protected boolean createKeys(List<Customer> list) {
// get this ability from the lookup ...
ReloadableQueryCapability r = query.getLookup().lookup(ReloadableQueryCapability.class);
// ... and use the ability
if (r != null) {
try {
r.reload();
} catch (Exception e) {
// Empty
}
}
// Now populate the list of child entities...
list.addAll(query.getCustomers());
// And return true since we're all set
return true;
}
@Override
protected Node createNodeForKey(Customer key) {
Node customernNode = new AbstractNode(Children.LEAF);
customernNode.setDisplayName(key.getName());
return customernNode;
}
}Finally, we create our root node. This is where we hook in our ReloadAction, as well as the RootNodeChildFactory:
import javax.swing.Action;
import org.my.api.CustomerQuery;
import org.my.api.ReloadableViewCapability;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
public final class RootNode extends AbstractNode {
private CustomerQuery query;
private InstanceContent instanceContent;
public RootNode(CustomerQuery query) {
this(query, new InstanceContent());
}
private RootNode(CustomerQuery query, InstanceContent ic) {
super(Children.create(new RootNodeChildFactory(query), true), new AbstractLookup(ic));
this.query = query;
this.instanceContent = ic;
// Add a new ability for this node to be reloaded
this.instanceContent.add(new ReloadableViewCapability() {
@Override
public void reloadChildren() throws Exception {
// To reload this node just set a new set of children
// using a RootNodeChildFactory object, that retrieves
// children asynchronously
setChildren(Children.create(new RootNodeChildFactory(RootNode.this.query), false));
}
});
}
@Override
public String getDisplayName() {
return "Query: " + query.getSqlstring();
}
@Override
public Action[] getActions(boolean context) {
// Pass the Lookup, which now contains ReloadableViewCapability, to the Action:
return new Action[]{new ReloadAction(getLookup())};
}
}As usual, create a new TopComponent. In it, create a new query object, populate the root node (which creates the children), using ExplorerManager.Provider and BeanTreeView:
CustomerQuery query = new CustomerQuery();
query.setSqlstring("SELECT c FROM Customer c");
RootNode node = new RootNode(query);
em.setRootContext(node);
setLayout(new BorderLayout());
add(new BeanTreeView(), BorderLayout.CENTER);
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
What you have done above is create a small demo application (a small subset of Antonio's application) using reload capabilities that are added to the Lookup of objects (the query and the root node) and then retrieved from it where needed.
Next, once the above has been commented on by Antonio and others, I'd like to see how the above works in a SaveCookie in a foreign module.
Note: Continue with part 2 (on save capability) and part 3 (on create capability).
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





Comments
Antonio Vieiro replied on Fri, 2011/04/29 - 5:35am
Hi,
Looks great. What about creating a CustomerEntity that has some "insert()", "update()" and "delete()" abilities? ;-)
Cheers,
Antonio
P.D.: I'll have to udpate the artitle with some JPA examples too!
Geertjan Wielenga replied on Fri, 2011/04/29 - 5:45am
A better ReloadAction would be like this:
And, in the RootNode:
Toni Epple replied on Mon, 2011/05/02 - 11:02am
Hi Geertjan,
great tutorial!
Right now the two main benefits of you implementation are that you're shielding the UI from the implementation details (JPA), and you can add additional capabilities without breaking backward compatibility by using Composition instead of inheritance.
Maybe you could enhance it by further decoupling:
If I understand it correctly, there's no way to declaratively swap implementations, because RootNode knows the CustomerQuery class, which in turn knows DAO, which knows the PU and everything else, right? So in the end the Ui is still coupled to the implementation.
You could place Customer and Discount in a separate Module from the Derby.jar, so they can be reused without JPA. CustomerQuery could implement an SPI (ICustomerQuery) and register itself as a service implementing the interface in default Lookup. Then RootNode can retrieve the registered Service from the Lookup to CRUD the DataObjects (Customer, Discount) and you could easily replace "JPADerbyCustomerQuery" with a different impl (e.g. based on serializing to files).
Cheers,
Toni
Matthew Russell replied on Tue, 2011/09/27 - 4:15am
I have quick question though...
Unless I'm understanding the code incorrectly, the reload action minimizes the children nodes underneath the root node. I've worked around this by creating a new action "expand" that fires a function call to the viewer to expand the root node. Is there a better way to auto expand the root node on a refresh?
Matthew Russell replied on Wed, 2011/09/28 - 6:11am
in response to: mrussell
Carlos Delgado replied on Fri, 2011/10/14 - 4:37am
in response to: geertjan
Hello Geertjan,
I changed my code to retrieve the available actions for a particular path, as you mention above, but the actions appear disabled.
Any idea why?
Thanks, and also thanks for your tutorials, great stuff!
Carlos