Creating Context-Sensitive Capabilities for File-Based Nodes (Epilogue)

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:

<folder name="Connections">
    <folder name="Cruise">
        <file name="iBatis">
            <attr name="config" stringvalue="resources/CruiseSqlMapConfig.xml"/>
        </file>
    </folder>
</folder>

On the other hand, someone providing configuration files for JPA would register their configuration file like this:

<folder name="Connections">
    <folder name="Cruise">
        <file name="JPA">
            <attr name="config" stringvalue="resources/CruisePersistence.xml"/>
        </file>
    </folder>
</folder>

"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:

<folder name="Connections">
    <folder name="Cruise">
        <file name="iBatis">
            <attr name="config" stringvalue="resources/CruiseSqlMapConfig.xml"/>
        </file>
        <file name="JPA">
            <attr name="config" stringvalue="resources/CruisePersistence.xml"/>
        </file>
    </folder>
    <folder name="Logbook">
        <file name="iBatis">
            <attr name="config" stringvalue="resources/LogbookSqlMapConfig.xml"/>
        </file>
        <file name="JPA">
            <attr name="config" stringvalue="resources/LogbookPersistence.xml"/>
        </file>
    </folder>
</folder>

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:

<folder name="DataServiceProviders">
    <folder name="Cruise">
        <folder name="iBatis">
            <file name="no-imr-viewer-serviceproviders-iBatisDataServiceProvider.instance"/>
        </folder>
        <folder name="JPA">
            <file name="no-imr-viewer-serviceproviders-JPADataServiceProvider.instance"/>
        </folder>
    </folder>
    <folder name="Logbook">
        <folder name="iBatis">
            <file name="no-imr-viewer-serviceproviders-iBatisDataServiceProvider.instance"/>
        </folder>
        <folder name="JPA">
            <file name="no-imr-viewer-serviceproviders-JPADataServiceProvider.instance"/>
        </folder>
    </folder>
</folder>

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<Cruise> {

    @Override
    protected boolean createKeys(List<Cruise> 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<Cruise> 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.

Comments:

Hi Geertjan,

what I don't like about this approach is the childfactory part: The childfactory is the consumer of the service. It should be transparent to the childfactory where the data comes from. The problem is this code:

DataServiceInterface dsi = Lookups.forPath("DataServiceProviders/Cruise/JPA").lookup(DataServiceInterface.class);

This way we're loosing the whole decoupling, because the Childfactory chooses the implementation.

The childfactory should only call:

DataServiceInterface dsi = Lookups.getDefault().lookup(DataServiceInterface.class);
List<Cruise> cruises = dsi.getData("Cruise");

It get's the data from the default lookup. Then the DataServiceInterface should decide which implementation to use, by calling:
DataServiceInterface dsi = Lookups.forPath("DataServiceProviders/Cruise").lookup(DataServiceInterface.class);

In fact it would be better to have a different interface for this, because we don't require the name parameter, also it would be easier to separate the main "Proxy" DataServiceInterface from the individual instances:

public interface NamedDataServiceInterface {

public List getData();

public void updateData();

public void saveData();

}

And as such choosing one implementation available there. I do not see why there should be multiple ones for the same db at the same time.

In \*our specific project\* there might be one reason, why there could be more than provider: We could use the central database, when connected to the internet, and a local one offline.

But that's a different use case and requires some additional logic.

--Toni

Posted by Toni Epple on April 21, 2010 at 02:43 AM PDT #

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
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today