Mmm. Sorry slipping into irrelevant babbling. What I want out of an API is to define an interface and have that magically fill in the blanks. So for example if I define:
public interface Queue { int getCount();
}
then all I'd have to do would be add some annotations to tell the Magic what I want and it would go ahead and make it happen. So today I created a prototype.
First the annotation:
package playtest;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Execute {
String script();
}
You then annotate the method you want to be magical.
package playtest;public interface Queue {
@Execute(script="{? = call Queue.getCount}")Integer getCount();
}
Next the magic. Note: I'm using a Guice provider to get hold of the database. I'm not 100% that is the right way to go as while I'm very likely to use Guice do I really want to tie the implementation down to Guice? This play test of an implementation can only return void or Integer. If I was to go forward with this I'd have to get it to be able to return all primitive types at least and ideally be able to marshal complex types. Likely via annotations in the complex types. Maybe what I really want is JPA.
package playtest;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.inject.Provider;
public class QuickAndDirtySQLHandler implements InvocationHandler {
private final Logger log = Logger.getLogger(getClass().getName());
private final Provider connectionProvider;
public QuickAndDirtySQLHandler(final Provider connectionProvider) {
this.connectionProvider = connectionProvider;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Execute execute = method.getAnnotation(Execute.class);
if (execute == null) {
throw new IllegalStateException("Required annotation missing!");
}
boolean returnsValue = false;
if (method.getReturnType().equals(Integer.c lass)) {
returnsValue = true;
}
Connection connection = connectionProvider.get();
CallableStatement statement = null;
try {
statement = connection.prepareCall(execute.script()) ;
if (returnsValue) {
statement.registerOutParameter(1, Types.INTEGER);
}
statement.execute();
if (returnsValue) {
return statement.getInt(1);
} else {
return null;
}
} finally {
close(statement);
connection.close();
}
}
private final Logger log = Logger.getLogger(getClass().getName());
private final Provider
public QuickAndDirtySQLHandler(final Provider
this.connectionProvider = connectionProvider;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Execute execute = method.getAnnotation(Execute.class);
if (execute == null) {
throw new IllegalStateException("Required annotation missing!");
}
boolean returnsValue = false;
if (method.getReturnType().equals(Integer.c
returnsValue = true;
}
Connection connection = connectionProvider.get();
CallableStatement statement = null;
try {
statement = connection.prepareCall(execute.script())
if (returnsValue) {
statement.registerOutParameter(1, Types.INTEGER);
}
statement.execute();
if (returnsValue) {
return statement.getInt(1);
} else {
return null;
}
} finally {
close(statement);
connection.close();
}
}
private void close(Statement s) {
if (s == null) {
return;
}
try {
s.close();
} catch(SQLException sqle) {
log.log(Level.WARNING, "Failed to close statement", sqle);
}
}
}
Next a helper class to handle database access.
package playtest.db;import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLException;
import org.apache.commons.dbcp.BasicDataSource;
import com.google.inject.Provider;
public class ExampleDatabaseProvider implements Provider{
private final BasicDataSource ds = new BasicDataSource();
public ExampleDatabaseProvider(String driver, String username,
String password, String url) {
ds.setDriverClassName(driver);
ds.setUsername(username);
ds.setPassword(password);
ds.setUrl(url);
ds.setMaxActive(1);
ds.setDefaultAutoCommit(false);
}
@Override
public Connection get() {
try {
return ds.getConnection();
} catch(SQLException sqle) {
// Switch to a throwing provider?
throw new IllegalStateException(sqle);
}
}
public void close() throws SQLException {String password, String url) {
ds.setDriverClassName(driver);
ds.setUsername(username);
ds.setPassword(password);
ds.setUrl(url);
ds.setMaxActive(1);
ds.setDefaultAutoCommit(false);
}
@Override
public Connection get() {
try {
return ds.getConnection();
} catch(SQLException sqle) {
// Switch to a throwing provider?
throw new IllegalStateException(sqle);
}
}
ds.close();
}
}
And finally some magic:
package playtest;import java.lang.reflect.Proxy;
import playtest.db.ExampleDatabaseProvider;
public class KickStart {public static void main(String argv[]) throws Exception {
ExampleDatabaseProvider dbProvider =
new ExampleDatabaseProvider(
"jdbc.driver","username", "password", "jdbc:database");
Queue example = (Queue)Proxy.newProxyInstance(
KickStart.class.g
new Class[] {Queue.class},
new QuickAndDirtySQLHandler(dbProvider));
System.out.println(example.getCount());
dbProvider.close();
}
}
I think that would be much better than raw JDBC for projects that demand the use of stored procedures (like all projects at my current employer). I should really look at how JPA handles forced use of stored procedures.
I think this is a cool idea and I plan to run with it (if I can find the time). I'll post updates here under the tag magic-procedures. The GitHub site is: http://github.com/mlk/magic-procedures and the public repository is git@github.com:mlk/magic-procedures.git

0 comments:
Post a Comment