Saturday, August 9, 2008

Closures: Database Example revisited



Neal Gafter's closures prototype http://www.javac.info/closures.tar.gz is now feature-complete and, among others, supports returning from inside an unrestricted closure to a target outside the closure. This was for me an excellent reason to revisit my database example and refactor its code.


My database example uses the sample database that comes with Netbeans. Start the database server (on a Unix based platforn using asadmin start-database in your ~/.netbeans-derby directory).


A good way to learn about Java closures is via Neal Gafter's JavaOne 2008 Closures Cookbook talk. You can download the slides here.




Before starting let's briefly try to convince the non-believers (are there still any out there?) about the usefulness of closures from a conceptual point of view. The example illustrates this usefulness from a practical point of view. Of course, this conceptual point of view is necessarily informal (this post is not about the mathematics behind it all).


Compositionality is a useful thing: it enables us to recursively build components from atomic components. When realized in an appropriate way, compositionality enables component reuse.


  • for information, compositionality is realized in Java by letting instance variables of an object, recursively, refer to objects
  • for functionality, compositionality is realized in Java by letting methods, invoked on an object, recursively, invoke methods on objects

Parameterization is a useful thing: it enables us to recursively plug components into component templates. When realized in an appropriate way, parameterization enables component template reuse..

  • for information, substitution is realized in Java by giving classes class parameters
  • for functionality, substitution is realized in Java by giving methods object parameters

So, as far as functionality substitution is concerned, the Java realization is sub-optimal: we need an extra object layer to obtain methods. Of course, is technically possible to use anonymous instances of anonymous classes implementing a one-method interface (compare this with the need for a calculator to do a calculation, or the need for a manager to do management, or the need for a printer to do a print job) : what an overkill, not very elegant!. This is where closures come to the rescue: they are, rougly speaking, blocks of code that can be used both as objects and methods. Using closures it is not not only possible to reuse blocks of code, but also blocks of code templates in an elegant way.




When processing a result set that has been obtained via a data source by executing a query, the following coding standards could have been imposed company wide:


  • resource related

    • library code: all allocated database resourses should be deallocated after usage

  • exception related

    • library code: all database related exceptions should be wrapped as meaningful exceptions
    • application code: all those meaningful exceptions should be unwrapped, some logging should be done, and the application should exit



The allocated database resources are

  • connections
  • statements
  • result sets


Things that can go wrong at the database level are

  • problems while getting a connection
  • problems while creating a statement
  • problems while executing a query
  • problems while processing a result set
  • problems while closing a result set
  • problems while closing a statement
  • problems while closing a connection






Let's start with the second coding standard. This coding standard can be imposed in a general way using a DSL consisting of the closure processing methods wrapIf and unwrapAndExitIf:



public static <throws E> void wrapIf(
{ Exception => boolean } exceptionPredicate,
E wrappingException,
{ ==> void throws Exception } throwingBlock) throws E, Exception {
try {
throwingBlock.invoke();
} catch(Exception thrownException) {
if(exceptionPredicate.invoke(thrownException)) {
wrappingException.initCause(thrownException);
throw wrappingException;
} else {
throw thrownException;
}
}
}

public static void unwrapAndExitIf(
{ Exception => boolean } exceptionPredicate,
{ ==> void throws Exception } throwingBlock) throws Exception {
try {
throwingBlock.invoke();
} catch(Exception exception) {
if(exceptionPredicate.invoke(exception)) {
Logger logger = Logger.getLogger("");
logger.severe(
exception.getClass().getName() + " thrown\n" +
"cause: " + exception.getCause() + "\nexiting...");
System.exit(1);
} else {
throw exception;
}
}
}






Let's continue with the first coding standard. This coding standard can be imposed using a DSL consisting of the closure processing method withResultSet. Note that the code makes use of the previous closure processing methods for dealing with database related exceptions. It also makes use of a predicate isSQLException that is defined elsewhere.



private static { Exception => boolean } isSQLException
= SQLPredicates#isSQLException(Exception);

public static <Y, throws E> Y withResultSet(
DataSource dataSource, String query,
{ ResultSet ==> Y throws SQLException } resultSetProcessor) throws E,
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
Connection connection = null;
wrapIf(isSQLException, new GetConnectionException()) {
connection = dataSource.getConnection();
}
Statement statement = null;
wrapIf(isSQLException, new CreateStatementException()) {
statement = connection.createStatement();
}
ResultSet resultSet = null;
wrapIf(isSQLException, new ExecuteQueryException()) {
resultSet = statement.executeQuery(query);
}
try {
wrapIf(isSQLException, new ProcessResultSetException()) {
return resultSetProcessor.invoke(resultSet);
}
} finally {
if(resultSet != null) {
wrapIf(isSQLException, new CloseResultSetException()) {
statement.close();
}
}
if(statement != null) {
wrapIf(isSQLException, new CloseStatementException()) {
statement.close();
}
}
if(connection != null) {
wrapIf(isSQLException, new CloseConnectionException()) {
connection.close();
}
}
}
// still here?
return null;
}





We are ready now for using our closure processing methods in an application. It makes use of a predicate iswrappedSQLException that is defined elsewhere. Read the code as with the result set obtained from a data source by executing a query.



private void showProductInfo(DataSource dataSource) throws
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
withResultSet(ResultSet resultSet: dataSource, "select * from PRODUCT") {
while(resultSet.next()) {
System.out.println(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
}
}

private List<Product> getProductInfo(DataSource dataSource) throws
GetConnectionException, CreateStatementException,
ExecuteQueryException, ProcessResultSetException,
CloseResultSetException, CloseStatementException, CloseConnectionException {
List<Product> products = new LinkedList<Product>();
withResultSet(dataSource, "select * from PRODUCT",
{ ResultSet resultSet ==>
while(resultSet.next()) {
products.add(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
// returning from inside an unrestricted closure to a target outside the closure
return products;
});
}

private { Exception => boolean } isWrappedSQLException
= SQLPredicates#isWrappedSQLException(Exception);

// ...

unwrapAndExitIf(isWrappedSQLException) {
showProductInfo(dataSource);
}
unwrapAndExitIf(isWrappedSQLException) {
System.out.println(getProductInfo(dataSource));
}

// ...



It is instructive to throw other exceptions as well. For example, try something like:



// ...
while(resultSet.next()) {
if(Math.random()<0.05) throw new RuntimeException("RuntimeException");
System.out.println(
new Product(
resultSet.getInt("PRODUCT_ID"), resultSet.getString("DESCRIPTION")));
}
// ...





If you are interested in the code: luc duponcheel at gmail com

0 comments: