Monday, July 29, 2013

Thanks H2!

After struggling with Java persistency I was getting a tad desperate. It is not that hard to get any of the tutorials to work but virtually all tutorial are creating unwanted couplings and often have reams of dependencies (often hidden behind inconspicuous looking maven poms). In an OSGi environment you need a persistent solution that works well with components. This means that in general components should not be bound to a specific brand of database because this would make these components not very reusable, they would work only in environments that had the same database brand. I was getting a bit desperate because it was so hard to find a working combination that did it the OSGi way.

The OSGi way is that the database is provided as a service. By providing it as a service you allow the deployer (the person that provides your application a place to run, which is often yourself but it makes sense to separate these roles) to pick a database brand at runtime by deploying the proper bundle and configuring it. That is, as a user of the database this is the code in the OSGi way:

@Component
public class DatabaseUser {
   DataSource ds;

   @Reference
   void setDataSource( DataSource ds) {
     this.ds = ds;
   }
}


This model is very elegant, it uncouples a lot of details from the component. The component does not have to worry about database configuration, transaction model, passwords, pooling, and not even its brand. Obviously, a component can have special dependencies because it uses proprietary features of certain databases (SQL is treated by vendors more like a recommendation than a specification), these can always be reflected by Require-Capability headers. That said, careful components can restrict themselves to a lowest common denominator so that they can work with any or most  SQL databases.

This leaves us the problem how to get the DataSource object without specifying its brand (or password!) in our code/xml? In a perfect OSGi world we would have JDBC drivers that use Configuration Admin to specify the desired Data Sources; the driver bundle would just register these configured Data Sources. This requires a trivial amount of code since JDBC drivers already configure themselves with properties, a good match to Configuration Admin.

We did not choose this model during the OSGi Enterprise specification because we thought this might be too much work for the driver vendors. I guess that was the correct assessment since the even easier to implement DataSourceFactory service that we did specify is still rare. The Data Source Factory service provides methods to create configured ConnectionPoolDataSource, DataSource, Driver, and XADataSource objects. With this interface it is fortunately not very hard to make a generic component that registers our desired Data Source services:


@Component(configurationPolicy=REQUIRED)
public class ConfiguredDataSource extends DataSource{
   DataSourceFactory dsf;
   DataSource ds;

   @Activate
   void activate(Map map) throws Exception {
     Properties p = new Properties();
     p.putAll(map);
     ds = dsf.createDataSource(p);
   }

   public Connection getConnection() throws SQLException {
     return ds.getConnection();
   }
      
   public Connection getConnection(String username, String password) 
    throws SQLException {
    return ds.getConnection(username, password);
   }

   @Reference
   void setDataSourceFactory( DataSourceFactory ds) {
     this.dsf = dsf;
   }
}

Realize hat this code is much more flexible than it looks at first sight because it leverages Declarative Services's features. Through Configuration Admin you can bind it to different brands of databases, you can set passwords, configuration options, and instantiate multiple DataSource services. However, the hard part, as is sadly still too often the case with OSGi, is finding a bundle that implements this specification. Without much hope I looked at the H2 database that I was already using in my prototypes. It is a fine example of a Java library. Rarely have seen a product with so many features, so small (1Mb), and so few dependencies (0). It is delivered out of the box as an OSGi bundle. And it actually registers a Data Source Factory service! I also got H2 to work with OpenJPA the OSGi way since the OSGi Specifications mandates JPA to use the OSGi way for JDBC, though this did not work out of the box yet. If only more code was delivered like this ...

To conclude, Java persistence is still a mess in my personal opinion; it will require a lot of work to make it all work the OSGi way. However, at least there is light at the end of the tunnel. Thanks H2 guys for this good out of the box component experience, hope you set a widely followed precedent. You definitely gave me hope.

Peter Kriens @pkriens

P.S. If you want to experiment with this approach, look at OPS4j Pax JADBC, the created adapters for laggards in this area.

3 comments:

  1. More than a year ago I struggled with the same issues. I needed to connected to SQL Server and couldn't find anything OSGI compliant. I ended implementing a bundle that embeds the driver and provides a DataSourceFactory and a ManagedServiceFactory. The ManagedServiceFactory is used to create DataSource instances using config admin. I wanted to avoid keeping the jdbc connection parameters in persistence.xml and with this approach the database user only needs to define a reference to a DataSource with the proper datasource name. Something like this:
    @Reference(optional=false,target="(dataSourceName="+DATASOURCE_NAME+")")
    public void bindDataSource(DataSource dataSource) {...}
    But my greatest nuisance was making JPA work. Even using Eclipselink, a JPA implementation with good OSGI support, I had major headaches. If I had to choose again, I would keep away from JPA and use something "lighter"

    ReplyDelete
    Replies
    1. I couldn't agree more after my experiences and I will do my best to make JPA really easy to use in OSGi.
      To stay closer to the spec, you probably want to let the SQL server driver register a org.osgi.service.jdbc.DataSourceFactory. Apache Aries has implemented a container that can leverage this service, I got it to work with OpenJPA.

      Again, I think this has high priority and I will do my utmost.

      Delete
  2. Peter,

    Thanks a lot for this article about OSGi and JPA. I am looking forward for more articles on this topic.

    ReplyDelete