Since I stated that yesterday's blog entry would be the final part in this series on creating context-sensitive capabilities for file-based nodes, the fact that the series continues today can only mean that... this is the epilogue! (In fact, it might be Part 1 of the epilogue.)
The reason for yet another part in this series is that loosely coupled data sources could have varying ORM tools behind them. In other words, one module could provide entity classes that use iBatis to connect to a database, while another module could provide entity classes that use JPA (or Hibernate or something else) to connect to a database.
How do we deal with this situation? Well, good thing that the interface for dealing with data is so simple:public interface DataServiceInterface { public List getData(String name); public void updateData(String name); public void saveData(String name); }
In fact, anything at all could be handled by means of the above interface. Let's say that our generic container application should also be able to display a node representing data coming from a web service. Not a problem, our interface covers that scenario too, doesn't it?
Great, so we have a very flexible interface, which can be implemented in many different ways. The only thing we need is for the relevant database connection files of the respective ORM tool to be registered in the layer. Someone providing a module with configuration files for iBatis, for example, would have this in the layer:
On the other hand, someone providing configuration files for JPA would register their configuration file like this:
"Connections" is the folder name containing the connections, below that is an identifier (here "Cruise") for distinguishing between different connections, because in the end, when the NetBeans Platform resolves all the layer files, the "Connections" folder will potentially have content like this:
So, now we have modules providing entity classes (some of which, as with JPA, have annotations, hence dependencies on EclipseLink or TopLink or something similar), while others are unannotated entity classes (such as those supported by iBatis). These modules all register their ORM configuration file, exactly as shown above.
And how are the above registrations used? That depends on the implementation of our DataServiceInterface. In the case of iBatis, the implementation of "getData" is as follows. Note the lines in bold, which shows you how the correct configuration file is found, i.e., if you want to use iBatis to get data from the "Cruise" database, all you need to do is provide the identifier "Cruise", followed by the name of the ORM, which is "iBatis" here, and "config" to specify the configuration file. As a user of iBatis in this application, you don't care at all about the implementation, you simply specify a way to find the file that you'll be using.@ServiceProvider(service = DataServiceInterface.class)public class iBatisDataServiceProvider implements DataServiceInterface { private String getConnectionFolder(String name) { return FileUtil.getConfigFile("Connections/" + name +"/iBatis").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 } }
The module providing the class above could now be in a different module to where the connection files are found, since the class above looks in the layer for the location of the connection file.
The above file is registered in the "DataServiceProviders" layer file of the module that provides it. When the NetBeans Platform resolves all layer files, it will end up with a "DataServiceProviders" folder with this content:
Now let's call "getData" in our implementation classes! When do we do that? When we need to get data. Where do we need to do that? In the loosely coupled ChildFactory classes (see the past entries in this series for details on this). These need to look inside the layer file and find the above folders, turning them into a Lookup, by means of which the implementation can be found, where we can call the "getData", since that is defined in the interface:public class CruisesChildFactory extends ChildFactory { @Override protected boolean createKeys(List list) { //Here we locate the implementation that will use JPA: DataServiceInterface dsi = Lookups.forPath("DataServiceProviders/Cruise/JPA").lookup(DataServiceInterface.class); //Here we pass "Cruise" to the implementation, so that the configuration file can be found: List cruises = dsi.getData("Cruise"); list.addAll(cruises); return true; } @Override protected Node createNodeForKey(Cruise key) { try { return new BeanNode(key); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } return null; } }
Above, we specify that we want to use JPA, without caring at all about the implementation. We simply say "yes, we want to use JPA for this connection", with the file registered in the "config" attribute determining how the connection is made, as always.
What is the point of all of this? To make the application very flexible, so that contributions from the outside can be made possible. In the scenario that the IMR (Institute of Marine Research) in Norway is working with, new nodes can come from a wide variety of third parties, each providing a broad range of different (unrelated) kinds of data. In this scenario, where the application is a generic container for nodes, a very loose structure such as the above seems to be of fundamental importance.


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