Статьи

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

У Антонио есть интересная серия под названием NetBeans Platform для автономных приложений Swing . Я особенно внимательно следил за частями, относящимися к «перезагружаемым» возможностям, поскольку я думаю, что область «возможностей» является одной из наиболее мощных, но наименее понятных частей API-интерфейсов NetBeans.

Итак, ниже я воспроизвожу небольшую часть сценария чириканья Антонио, но преобразован в сценарий базы данных. У меня все еще есть открытые вопросы обо всем этом, но в любом случае это начало обсуждения.

  1. Начните с создания нового приложения на платформе NetBeans с именем «CustomerCRUD» или как угодно, как бы вы хотели его называть.
  2. Создайте сущность Customer (со связанной сущностью Discount) с помощью мастеров в IDE, как описано в первых частях учебного руководства по приложениям NetBeans CRUD . Затем у вас будет JAR, содержащий Customer.class, Discount.class и файл persistence.xml. Поместите этот JAR вместе с клиентским JAR сервера базы данных (например, DerbyClient.jar) и JAR, связанными с JPA (например, EclipseLink JAR), в модуль оболочки библиотеки (например, с именем «CustomerLibraries»), все из которых описаны в учебнике выше. Теперь у вас есть один модуль, модуль-оболочка библиотеки, который содержит около 4 разных JAR-файлов.
  3. Затем создайте второй модуль (например, с именем «CustomerAPI»), который будет содержать ваши классы API, а также возможности, которые должны быть доступны для других модулей. (Или, возможно, возможности должны быть в отдельном модуле «CustomerUtilities».) В этом модуле у нас будет четыре класса, все они описаны на следующем шаге.
  4. Мы начинаем с создания возможностей, которые мы будем повторно использовать в нашем приложении в слабосвязанной форме. Прежде всего, мы создадим возможность для перезагрузки сущностей, а затем возможность для перезагрузки нашего представления:

    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 завершен. Он содержит код доступа к базе данных, а также возможности перезагрузки, одну из которых мы уже используем в нашем объекте запроса для его перезагрузки.

  5. Создайте третий модуль (например, «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 (о возможности создания) .