Статьи

Слабосвязанные сохраняемые возможности для приложений CRUD

Теперь давайте сделаем еще один шаг и добавим возможности Saveable к сценарию, описанному в разделе «Слабосвязанные перезагружаемые возможности для приложений CRUD» . То есть, перед тем, как начать эту статью, предполагается, что вы читали предыдущую часть, в противном случае последующее не будет иметь никакого смысла.

Таким образом, цель состоит в том, чтобы использовать тот же «подход к возможностям» при добавлении функций сохранения в наше приложение.

Начнем с нашего DAO. В дополнение к методу search (), теперь у нас также есть метод save ():

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();
}

}

Как видите, все вышеперечисленное является чисто стандартным кодом JPA. Это хорошо, четкое разделение между нашим кодом API NetBeans и нашими функциями доступа к данным.

Теперь мы определяем возможность, которую мы собираемся реализовать в нашем объекте запроса. Объект запроса требует, помимо возможности перезагрузки, возможности сохранения. Поэтому в отдельном классе в нашем модуле API мы определяем нашу новую возможность:

public interface SaveableEntityCapability {

public void save(Customer customer) throws Exception;

}

Далее мы реализуем нашу новую возможность в объекте запроса. Единственное изменение в объекте запроса со вчерашнего дня — это добавление SaveEntityCapability к InstanceContent объекта запроса, который вы можете увидеть в последнем блоке ниже:

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);
}
});
}

Итак, позже мы сможем извлечь SaveableEntityCapability из Lookup объекта запроса и затем вызывать его метод Save всякий раз, когда нам нужно сохранить объект Customer.

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.