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:
And then, in the definition of the ChildFactory, the connection is made to the database: public class CruisesChildFactory extends ChildFactory { @Override protected boolean createKeys(List list) { DataServiceInterface dsi = Lookup.getDefault().lookup( ass); List 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 functionaly must (as can be seen in the code above), register their iBatis map file as follows in the layer:
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, which is part of the core distribution, while the implementation comes from a separate module:

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

Read More about [Creating Context-Sensitive Capabilities for File-Based Nodes (Part 4) ...