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

Loosely Coupled Saveable Capabilities for CRUD Applications

04.30.2011
| 8843 views |
  • submit to reddit

Let's now go a step further and add Saveable capabilities to the scenario outlined in Loosely Coupled Reloadable Capabilities for CRUD Applications. I.e., before beginning this article, the assumption is that you've read the previous part, otherwise what follows will make no sense at all.

So, the aim is to use the same "capabilities approach" in adding save functionality to our application.

We start with our DAO. In addition to the "search()" method, we now also have a "save()" method:

import client.Customer;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;

public final class CustomerSearchDAO {

EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerPU").createEntityManager();

public List<Customer> search(String search) {
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;
}

public void save(Customer customer) {
entityManager.getTransaction().begin();
entityManager.find(Customer.class, customer.getCustomerId());
entityManager.getTransaction().commit();
}

}

As you can see, the above is all pure standard JPA code. That's nice, a clean separation between our NetBeans API code and our data access functionality.

Now we define a capability that we're going to implement in our query object. The query object needs, in addition to a capability for reloading, a capability for saving. Therefore, in a separate class in our API module, we define our new capability:

public interface SaveableEntityCapability {

public void save(Customer customer) throws Exception;

}

Next, we implement our new capability in the query object. The only change to the query object from yesterday is the addition of the SaveEntityCapability to the InstanceContent of the query object, which you can see in the last block below:

public CustomerQuery() {
customers = new ArrayList();
// 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 ReloadableEntityCapability() {
@Override
public void reload() throws Exception {
ProgressHandle handle = ProgressHandleFactory.createHandle("Loading...");
handle.start();
List result = dao.search(sqlstring);
for (Customer customer : result) {
if (!getCustomers().contains(customer)) {
getCustomers().add(customer);
}
}
handle.finish();
}
});
instanceContent.add(new SaveableEntityCapability() {
@Override
public void save(Customer customer) throws Exception {
dao.save(customer);
}
});
}

So, later we'll be able to retrieve the SaveableEntityCapability from the query object's Lookup and then call its Save method whenever we need to save a Customer object.

Now create a new module (named "MyEditor", for example). In the new module, create a new TopComponent (named "MyEditorTopComponent", for example). In this TopComponent, create a JTextField that will display the name of the currently selected customer. I.e., the user will select a customer node and then the current customer's name will appear in the editor TopComponent.

In the editor TopComponent, we're going to create a SaveCookie implementation as follows:

private class SaveableViewCapability implements SaveCookie {
@Override
public void save() throws IOException {
SaveableEntityCapability saveable = query.getLookup().lookup(SaveableEntityCapability.class);
try {
customer.setName(nameField.getText());
saveable.save(customer);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
ReloadableEntityCapability r = query.getLookup().lookup(ReloadableEntityCapability.class);
try {
r.reload();
} catch (Exception e) {
}
ReloadableViewCapability rvc = customerNode.getLookup().lookup(ReloadableViewCapability.class);
try {
rvc.reloadChildren();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
fire(false);
}
}

The above is the key to understanding everything discussed in this article. What you have above is a SaveCookie which is, essentially, a "SaveableViewCapability". So, in the same way that yesterday we had a capability pair of "ReloadableEntityCapability / ReloadableViewCapability", we now have "SaveableEntityCapability / SaveableViewCapability". From the view capability, which is the SaveCookie, we first call the model capability (the "SaveEntityCapability"), after which we need to reload the entity, followed by a reload of the view.

So, the above is completely correct and exactly the order of capabilities that we would expect.

Note that for the above to be possible, we need to have access to the query, as well as to the Customer. After all, the query provides implementations for saving Customers and reloading itself. Then, we also need to have access to the Node, since the Node needs to have an implementation of the capability for itself to be reloaded.

And here is the Node, i.e., the CustomerNode. The CustomerNode needs to expose the customer and the query, together with an implementation of its capability of being reloaded:

import client.Customer;
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 CustomerNode extends AbstractNode {

public CustomerNode(Customer customer, CustomerQuery query) {
this(customer, query, new InstanceContent());
}

private CustomerNode(final Customer customer, final CustomerQuery query, InstanceContent ic) {
super(Children.LEAF, new AbstractLookup(ic));
final String oldName = customer.getName();
ic.add(customer);
ic.add(query);
ic.add(new ReloadableViewCapability() {
@Override
public void reloadChildren() throws Exception {
String newName = customer.getName();
fireDisplayNameChange(oldName, newName);
}
});
}

@Override
public String getDisplayName() {
Customer c = getLookup().lookup(Customer.class);
return c.getName();
}

}

The CustomerNode is created from the RootNodeChildFactory, as follows:

import client.Customer;
import java.util.List;
import org.my.api.CustomerQuery;
import org.my.api.ReloadableEntityCapability;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;

class RootNodeChildFactory extends ChildFactory {

private CustomerQuery query;

public RootNodeChildFactory(CustomerQuery query) {
this.query = query;
}

@Override
protected boolean createKeys(List list) {
// get this ability from the lookup ...
ReloadableEntityCapability r = query.getLookup().lookup(ReloadableEntityCapability.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) {
return new CustomerNode(key, query);
}

}

Finally, you need to listen for multiple objects in the EditorTopComponent. Not only should you be listening for the Customer object and the query object, but also to the Node:

@Override
public void componentOpened() {
customerNodeResult = Utilities.actionsGlobalContext().lookupResult(Node.class);
customerResult = Utilities.actionsGlobalContext().lookupResult(Customer.class);
customerQueryResult = Utilities.actionsGlobalContext().lookupResult(CustomerQuery.class);
customerNodeResult.addLookupListener(this);
customerQueryResult.addLookupListener(this);
customerResult.addLookupListener(this);
}

@Override
public void componentClosed() {
customerNodeResult.removeLookupListener(this);
customerQueryResult.removeLookupListener(this);
customerResult.removeLookupListener(this);
}

And, in the "resultChanged" in the EditorTopComponent, set global variables, for each of the objects you're interested in:

@Override
public void resultChanged(LookupEvent le) {
//Get the query:
Collection allQueries = customerQueryResult.allInstances();
Iterator it1 = allQueries.iterator();
while (it1.hasNext()) {
query = it1.next();
setDisplayName(query.getSqlstring());
}
//Get the customer:
Collection allCustomers = customerResult.allInstances();
Iterator it2 = allCustomers.iterator();
while (it2.hasNext()) {
customer = it2.next();
jTextField1.setText(customer.getName());
}
//Get the node:
Collection allNodes = customerNodeResult.allInstances();
Iterator it3 = allNodes.iterator();
while (it3.hasNext()) {
customerNode = it3.next();
}
}

And now you can understand the SaveCookie implementation better (which is added to the Lookup of the TopComponent, via InstanceContent) shown earlier, reproduced again below:

private class SaveableViewCapability implements SaveCookie {
@Override
public void save() throws IOException {
SaveableEntityCapability saveable = query.getLookup().lookup(SaveableEntityCapability.class);
try {
customer.setName(jTextField1.getText());
saveable.save(customer);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
ReloadableEntityCapability r = query.getLookup().lookup(ReloadableEntityCapability.class);
try {
r.reload();
} catch (Exception e) {
}
ReloadableViewCapability rvc = customerNode.getLookup().lookup(ReloadableViewCapability.class);
try {
rvc.reloadChildren();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
fire(false);
}
}

And now the "Save" capabilities, consisting of a save view capability and a save entity capability, should do exactly what you would expect. Automatically, whenever a change is saved in the editor TopComponent, the viewer TopComponent is updated.

 

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

Comments

Lukman Adekunle replied on Mon, 2011/05/23 - 5:35am

Hi Geertjan Looking through one of your above codes. I discovered that you were suppose to put the merge method instead of find method of the entity manager reference.

public void save(Customer customer) {  
entityManager.getTransaction().begin();
entityManager.find(Customer.class, customer.getCustomerId());
entityManager.getTransaction().commit();
}

 I would also like to say that having the full source of the code using the trip example would be fine. If it is possible.

Martin Vondráček replied on Mon, 2012/01/30 - 5:52pm

You have changed ReloadableQueryCapability into ReloadableEntityCapability, right? 

Martin Vondráček replied on Sun, 2012/02/05 - 3:33pm

(resolved, my stupid fault, and deleted)

Matt Coleman replied on Fri, 2013/03/15 - 2:30am in response to: Lukman Adekunle

thanks for pointing this out Lukman

 buffalo logo design 

Danish12 Ali replied on Wed, 2013/08/21 - 2:40am

 Today is a great day not just for California but for all Americans, who will want to now come visit this geological and ecological wonder," Farr said in a statement.  Thomson Three

Danish12 Ali replied on Sat, 2013/10/26 - 5:03am

 Dude! I really like your collection. It impressed me too much  https://rebelmouse.com

Comment viewing options

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