У Антонио есть интересная серия под названием NetBeans Platform для автономных приложений Swing . Я особенно внимательно следил за частями, относящимися к «перезагружаемым» возможностям, поскольку я думаю, что область «возможностей» является одной из наиболее мощных, но наименее понятных частей API-интерфейсов NetBeans.
Итак, ниже я воспроизвожу небольшую часть сценария чириканья Антонио, но преобразован в сценарий базы данных. У меня все еще есть открытые вопросы обо всем этом, но в любом случае это начало обсуждения.
- Начните с создания нового приложения на платформе NetBeans с именем «CustomerCRUD» или как угодно, как бы вы хотели его называть.
- Создайте сущность Customer (со связанной сущностью Discount) с помощью мастеров в IDE, как описано в первых частях учебного руководства по приложениям NetBeans CRUD . Затем у вас будет JAR, содержащий Customer.class, Discount.class и файл persistence.xml. Поместите этот JAR вместе с клиентским JAR сервера базы данных (например, DerbyClient.jar) и JAR, связанными с JPA (например, EclipseLink JAR), в модуль оболочки библиотеки (например, с именем «CustomerLibraries»), все из которых описаны в учебнике выше. Теперь у вас есть один модуль, модуль-оболочка библиотеки, который содержит около 4 разных JAR-файлов.
- Затем создайте второй модуль (например, с именем «CustomerAPI»), который будет содержать ваши классы API, а также возможности, которые должны быть доступны для других модулей. (Или, возможно, возможности должны быть в отдельном модуле «CustomerUtilities».) В этом модуле у нас будет четыре класса, все они описаны на следующем шаге.
- Мы начинаем с создания возможностей, которые мы будем повторно использовать в нашем приложении в слабосвязанной форме. Прежде всего, мы создадим возможность для перезагрузки сущностей, а затем возможность для перезагрузки нашего представления:
public interface ReloadableQueryCapability {
public void reload() throws Exception;
}public interface ReloadableViewCapability {
public void reloadChildren() throws Exception;
}Затем мы создаем объект доступа к данным, который будет обращаться к базе данных через строку SQL, переданную в нее:
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;
}
}И мы заканчиваем наш модуль API, создавая объект запроса, которому мы хотели бы назначить возможность его перезагрузки. Поэтому нам нужно реализовать класс Lookup.Provider и вернуть Lookup, который содержит реализацию 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;
}
}Наш модуль API завершен. Он содержит код доступа к базе данных, а также возможности перезагрузки, одну из которых мы уже используем в нашем объекте запроса для его перезагрузки.
- Создайте третий модуль (например, «CustomerViewer»), в котором будет создано все, что описано в этом шаге. Сначала мы создаем новое действие, которое получит запрос. В Lookup мы ищем ReloadableViewCapability и, если он есть, мы вызываем метод реализации reloadChildren. То, что мы не знаем, зависит от реализации, которую мы получаем из передаваемого в Lookup.
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);
}
}
}
}Затем, в том же модуле, мы создаем фабричный класс, который позже мы прикрепим к корневому узлу нашего представления. Фабричный класс выполняет запрос, извлеченный из Lookup запроса, и теперь вызов getCustomers для запроса содержит текущее состояние базы данных.
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;
}
}Наконец, мы создаем наш корневой узел. Вот где мы подключаем наш ReloadAction, а также 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())};
}
}Как обычно, создайте новый TopComponent. В нем создайте новый объект запроса, заполните корневой узел (который создает дочерние элементы), используя ExplorerManager.Provider и 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()));
То, что вы сделали выше, — это создайте небольшое демонстрационное приложение (небольшое подмножество приложения Антонио), используя возможности перезагрузки, которые добавляются в Lookup объектов (запрос и корневой узел), а затем извлекаются из него при необходимости.
Далее, как только Антонио и другие прокомментировали вышесказанное, я хотел бы посмотреть, как это работает в SaveCookie во внешнем модуле.
Примечание. Перейдите к части 2 (о возможности сохранения) и части 3 (о возможности создания) .