Creating Context-Sensitive Capabilities for File-Based Nodes (Part 4)

In this the final part of this ad-hoc series on context-sensitive actions for file-based nodes... we'll do something pretty radical—we'll use the loosely coupled ChildFactories from yesterday's blog entry to provide loosely coupled data sources. That's exactly what the Institute of Marine Research (IMR) in Bergen, Norway, needs.

Look at the screenshots below, you see two nodes, each providing access to different data... in the same TopComponent. While I integrated with the 'sample' database that comes with NetBeans IDE, the IMR will be integrating databases from multiple different external parties. One of these will provide support for the logbooks database, while another will do so for the cruises database. These two have nothing in common, except that the underlying data needs to be displayed for editing purposes, in a common TopComponent, with each database being represented by a node in the tree hierarchy, exactly as below:

The two nodes above don't know anything about each other. Neither does the rest of the application know about the nodes. The application is simply a generic container that needs nothing more than these entries in the layer file of contributed modules in order to build nodes:

<file name="Cruises.imrc">
    <attr name="iconBase" stringvalue="/imr/details/customer.png"/>
    <attr name="displayName" stringvalue="Cruises"/>
    <attr name="window" stringvalue="DetailsTopComponent"/>
    <attr name="childFactoryClass" newvalue="imr.details.CruisesChildFactory"/>
</file>

And then, in the definition of the ChildFactory, the connection is made to the database:

public class CruisesChildFactory extends ChildFactory<Customer> {

    @Override
    protected boolean createKeys(List<Customer> list) {
        DataServiceInterface dsi = Lookup.getDefault().lookup(DataServiceInterface.class);
        List<Customer> customers = dsi.getData("customer");
        list.addAll(customers);
        return true;
    }

    @Override
    protected Node createNodeForKey(Customer key) {
         try {
            return new AnnotatedBeanNode(key);
        } catch (IntrospectionException ex) {
            Exceptions.printStackTrace(ex);
        }
        return null;
    }
    
}

(The "AnnotatedBeanNode" is by Toni Epple, part of his support for annotations that generate BeanInfo classes at runtime, about which more will be written in an upcoming blog entry or article.)

Notice that we load an implementation of the DataServiceInterface, of which there is only one, and pass in a string that is unique for the database that we want to have access to. And here's the implementation, for iBatis (which is currently the only implementation):

@ServiceProvider(service = DataServiceInterface.class)
public class iBatisDataServiceProvider implements DataServiceInterface {

    private String getConnectionFolder(String name) {
        return FileUtil.getConfigFile("Connections/" + name).getAttribute("config").toString();
    }

    @Override
    public List getData(String name) {
        Reader reader = null;
        try {
            reader = reader = Resources.getResourceAsReader(getConnectionFolder(name));
            SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
            return sqlMap.queryForList("getAll", null);
        } catch (SQLException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        } finally {
            try {
                reader.close();
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        return null;
    }

    @Override
    public void updateData(String name) {
       //to be done
    }

    @Override
    public void saveData(String name) {
       //to be done
    }
    
}

Each module wanting to hook into the above functionality must (as can be seen in the code above), register their iBatis map file as follows in the layer:

<folder name="Connections">
    <file name="customer">
        <attr name="config" stringvalue="resources/CruisesSqlMapConfig.xml"/>
    </file>
</folder>

And that's basically all that's needed to create a container for the display of data sources as nodes.

To make the solution really modular, for each data source, we have three modules—an API module (providing the entity classes), a DB configuration module (providing the iBatis map files), and a model module (providing the ChildFactory, registered as shown above, together with the icon displayed for the node). Here's how it all looks, take note of the service interface implementation, which is part of the core distribution, while the service interface comes from a separate module:

Later, one can imagine multiple implementations of the service (i.e., one for iBatis, one for JPA, one for Hibernate), all being provided by different modules.

The sources of the above sample will be available on Kenai soon.

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
23
24
25
26
27
28
29
30
   
       
Today