From e246af935f1f6b5395cd4d8d96b0ce44d70ecf91 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Mon, 3 Jul 2017 03:05:53 -0400 Subject: [PATCH] Tweak JdbcMapperFactory, now used for QueryMapper and JdbcMapper generated classes --- .../jdbc/codegen/JdbcMapperFactory.java | 72 +++++++++++-------- .../jdbc/codegen/JdbcMapperProcessor.java | 49 +++++-------- .../jdbc/codegen/PersonDAO.java | 2 +- .../codegen/PersonDAOQueryRunnerTest.java | 39 ++++++++++ .../com/moparisthebest/jdbc/QueryMapper.java | 36 ++-------- .../moparisthebest/jdbc/QueryRunnerTest.java | 2 +- 6 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAOQueryRunnerTest.java diff --git a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java index a445bef..fb9e3a5 100644 --- a/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java +++ b/common/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperFactory.java @@ -8,6 +8,8 @@ import javax.naming.NamingException; import javax.sql.DataSource; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.sql.Connection; import java.sql.SQLException; @@ -20,9 +22,23 @@ public class JdbcMapperFactory implements Factory { static final String SUFFIX = "Bean"; + static { + try{ + final Class ensureContext = Class.forName(System.getProperty("QueryMapper.ensureContext.class", System.getProperty("JdbcMapper.ensureContext.class", "com.gcl.containerless.EnsureContext"))); + final Method method = ensureContext.getMethod(System.getProperty("QueryMapper.ensureContext.method", System.getProperty("JdbcMapper.ensureContext.method", "setup"))); + method.invoke(null); + }catch(Throwable e){ + // ignore + //e.printStackTrace(); + } + } + @SuppressWarnings("unchecked") public static Class getImplementationClass(final Class jdbcMapper) throws ClassNotFoundException { - return (Class) Class.forName(jdbcMapper.getName() + SUFFIX); + if(jdbcMapper.isInterface() || Modifier.isAbstract(jdbcMapper.getModifiers())) + return (Class) Class.forName(jdbcMapper.getName() + SUFFIX); + else + return jdbcMapper; } public static Constructor getConnectionConstructor(final Class jdbcMapper) throws ClassNotFoundException, NoSuchMethodException { @@ -45,6 +61,14 @@ public class JdbcMapperFactory implements Factory { } } + public static T create(final Class jdbcMapper, final Factory connectionFactory) { + try { + return getFactoryConstructor(jdbcMapper).newInstance(connectionFactory); + } catch (Throwable e) { + throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e); + } + } + public static T create(final Class jdbcMapper) { try { return getDefaultConstructor(jdbcMapper).newInstance(); @@ -115,10 +139,22 @@ public class JdbcMapperFactory implements Factory { ; } + public static Factory of(final Class jdbcMapper) { + return new JdbcMapperFactory(jdbcMapper); + } + + public static Factory of(final Class jdbcMapper, final Factory connectionFactory) { + return new JdbcMapperFactory(jdbcMapper, connectionFactory); + } + + public static Factory of(final Class jdbcMapper, final String jndiName) { + return of(jdbcMapper, connectionFactory(jndiName)); + } + private final Constructor constructor; private final Object[] args; - public JdbcMapperFactory(final Class jdbcMapper) { + private JdbcMapperFactory(final Class jdbcMapper) { try { this.constructor = getDefaultConstructor(jdbcMapper); this.args = null; @@ -127,39 +163,19 @@ public class JdbcMapperFactory implements Factory { } } - public JdbcMapperFactory(final Constructor constructor, final Object... args) { - if (constructor == null) - throw new NullPointerException("constructor must be non-null"); - this.constructor = constructor; - this.args = args; - } - - public JdbcMapperFactory(final Class queryMapperClass, final Factory connectionFactory) { - if (queryMapperClass == null) - throw new NullPointerException("queryMapperClass must be non-null"); + private JdbcMapperFactory(final Class jdbcMapper, final Factory connectionFactory) { + if (jdbcMapper == null) + throw new NullPointerException("jdbcMapper must be non-null"); if (connectionFactory == null) throw new NullPointerException("connectionFactory must be non-null"); try { - this.constructor = queryMapperClass.getConstructor(Factory.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException("queryMapperClass must have a constructor that takes Factory", e); + this.constructor = getFactoryConstructor(jdbcMapper); + } catch (Throwable e) { + throw new RuntimeException("jdbcMapper must have a constructor that takes Factory", e); } this.args = new Object[]{connectionFactory}; } - public JdbcMapperFactory(final Class queryMapperClass, final String jndiName) { - if (queryMapperClass == null) - throw new NullPointerException("queryMapperClass must be non-null"); - if (jndiName == null) - throw new NullPointerException("jndiName must be non-null"); - try { - this.constructor = queryMapperClass.getConstructor(String.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException("queryMapperClass must have a constructor that takes String", e); - } - this.args = new Object[]{jndiName}; - } - @Override public T create() throws SQLException { try { diff --git a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java index da1b73b..5e9af22 100644 --- a/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java +++ b/jdbcmapper/src/main/java/com/moparisthebest/jdbc/codegen/JdbcMapperProcessor.java @@ -149,10 +149,7 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write(packageName); w.write(";\n\n"); } - if (doJndi) { - w.write("import javax.naming.InitialContext;\n"); - w.write("import javax.sql.DataSource;\n"); - } + w.write("import com.moparisthebest.jdbc.Factory;\n\n"); w.write("import java.sql.*;\n\n"); w.write("import static com.moparisthebest.jdbc.util.ResultSetUtil.*;\n"); w.write("import static com.moparisthebest.jdbc.TryClose.tryClose;\n\n"); @@ -164,36 +161,16 @@ public class JdbcMapperProcessor extends AbstractProcessor { w.write(" extends "); } w.write(genClass.getSimpleName().toString()); - w.write(" {\n\n\tprivate final Connection conn;\n"); + w.write(" {\n\n\tprivate final Connection conn;\n\tprivate final boolean closeConn;\n\n"); if (doJndi) { - w.write("\tprivate final InitialContext ctx;\n\n\tpublic "); + w.write("\tprivate static final Factory _conFactory = com.moparisthebest.jdbc.codegen.JdbcMapperFactory.connectionFactory(\""); + w.append(mapper.jndiName()).append("\");\n\n\tpublic "); w.write(className); - w.write("() {\n\t\tthis(null);\n\t}\n"); + w.write("() throws SQLException {\n\t\tthis(_conFactory);\n\t}\n"); } w.write("\n\tpublic "); w.write(className); - w.write("(Connection conn) {\n\t\t"); - if (doJndi) { - w.write("InitialContext ctx = null;\n" + - "\t\tif (conn == null)\n" + - "\t\t\ttry {\n" + - "\t\t\t\tctx = new InitialContext();\n" + - "\t\t\t\tDataSource ds = (DataSource) ctx.lookup(\""); - w.write(mapper.jndiName()); // todo: escape this? I don't think anyone needs that, for now... - w.write("\");\n" + - "\t\t\t\tconn = ds.getConnection();\n" + - "\t\t\t} catch (Throwable e) {\n" + - "\t\t\t\ttryClose(ctx);\n" + - "\t\t\t\ttryClose(conn);\n" + - "\t\t\t\tthrow new RuntimeException(e);\n" + - "\t\t\t}\n" + - "\t\tthis.conn = conn;\n" + - "\t\tthis.ctx = ctx;" - ); - } else { - w.write("this.conn = conn;"); - } - w.write("\n\t\tif (this.conn == null)\n" + + w.write("(Connection conn) {\n\t\tthis.conn = conn;\n\t\tthis.closeConn = false;\n\t\tif (this.conn == null)\n" + "\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n" + "\n\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n" ); @@ -458,10 +435,22 @@ public class JdbcMapperProcessor extends AbstractProcessor { if (cachedPreparedStatements > 0) w.write("\t\tfor(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n"); if (doJndi) - w.write("\t\ttryClose(ctx);\n\t\tif(ctx != null)\n\t\t\ttryClose(conn);\n"); + w.write("\t\tif(closeConn)\n\t\t\ttryClose(conn);\n"); if (closeMethod.getEnclosingElement().getKind() != ElementKind.INTERFACE && !closeMethod.getEnclosingElement().equals(genClass)) w.write("\t\tsuper.close();\n"); w.write("\t}\n"); + + // and we can create constructors that set closeConn to true! + w.write("\n\tpublic "); + w.write(className); + w.write("(final Factory connectionFactory) throws SQLException {\n\t\tthis.conn = connectionFactory.create();\n\t\tthis.closeConn = true;\n\t\tif (this.conn == null)\n" + + "\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n" + ); + + w.write("\n\tpublic "); + w.write(className); + w.write("(final String jndiName) throws SQLException {\n\t\tthis(com.moparisthebest.jdbc.codegen.JdbcMapperFactory.connectionFactory(jndiName));\n\t}\n" + ); } // end close method diff --git a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java index fdffc01..c02b43c 100644 --- a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAO.java @@ -28,7 +28,7 @@ import java.time.*; // , sqlParser = SimpleSQLParser.class , allowReflection = JdbcMapper.OptionalBool.TRUE ) -public interface PersonDAO extends Closeable { +public interface PersonDAO extends JdbcMapper { @JdbcMapper.SQL("CREATE TABLE person (person_no NUMERIC, first_name VARCHAR(40), last_name VARCHAR(40), birth_date TIMESTAMP)") void createTablePerson(); diff --git a/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAOQueryRunnerTest.java b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAOQueryRunnerTest.java new file mode 100644 index 0000000..d9e5cfd --- /dev/null +++ b/jdbcmapper/src/test/java/com/moparisthebest/jdbc/codegen/PersonDAOQueryRunnerTest.java @@ -0,0 +1,39 @@ +package com.moparisthebest.jdbc.codegen; + +import com.moparisthebest.jdbc.*; +import com.moparisthebest.jdbc.dto.Person; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.SQLException; + +import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1; +import static org.junit.Assert.assertEquals; + +/** + * Created by mopar on 7/3/17. + */ +public class PersonDAOQueryRunnerTest { + + public static final QueryRunner pqr = QueryRunner.withRetry(JdbcMapperFactory.of(PersonDAO.class, new Factory() { + @Override + public Connection create() throws SQLException { + return QueryMapperTest.getConnection(); + } + }), 10, QueryRunner.fixedDelay(5).withJitter(5)); + + @Test + public void testPerson() throws Exception { + //final QueryRunner lqr = pqr.withFactory(() -> new ListQueryMapper(QueryMapperTest::getConnection)); + assertEquals(fieldPerson1, pqr.runRetryFuture(new QueryRunner.Runner() { + @Override + public Person run(final PersonDAO dao) throws SQLException { + if(Math.random() < 0.5) { + System.out.println("fake fail"); + throw new SQLException("fake 50% failure rate"); + } + return dao.getPerson(fieldPerson1.getPersonNo()); + } + }).get()); + } +} diff --git a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java index 85e77b6..5a396aa 100644 --- a/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java +++ b/querymapper/src/main/java/com/moparisthebest/jdbc/QueryMapper.java @@ -1,13 +1,10 @@ package com.moparisthebest.jdbc; import com.moparisthebest.jdbc.codegen.JdbcMapper; +import com.moparisthebest.jdbc.codegen.JdbcMapperFactory; import com.moparisthebest.jdbc.util.ResultSetIterable; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.sql.DataSource; import java.io.*; -import java.lang.reflect.Method; import java.sql.*; import java.util.*; //IFJAVA8_START @@ -22,27 +19,16 @@ public class QueryMapper implements JdbcMapper { public static final Object noBind = new Object(); public static final ResultSetMapper defaultRsm = new ResultSetMapper(); - static { - try{ - final Class ensureContext = Class.forName(System.getProperty("QueryMapper.ensureContext.class", "com.gcl.containerless.EnsureContext")); - final Method method = ensureContext.getMethod(System.getProperty("QueryMapper.ensureContext.method", "setup")); - method.invoke(null); - }catch(Throwable e){ - // ignore - //e.printStackTrace(); - } - } - protected final ResultSetMapper cm; protected final Connection conn; - protected final Context context; protected final boolean closeConn; - protected QueryMapper(Connection conn, final String jndiName, final Factory factory, final ResultSetMapper cm) { + protected QueryMapper(Connection conn, final String jndiName, Factory factory, final ResultSetMapper cm) { this.cm = cm == null ? defaultRsm : cm; boolean closeConn = false; - Context context = null; if(conn == null) { + if(factory == null && jndiName != null) + factory = JdbcMapperFactory.connectionFactory(jndiName); if (factory != null) { try { conn = factory.create(); @@ -50,21 +36,11 @@ public class QueryMapper implements JdbcMapper { } catch (SQLException e) { throw new RuntimeException("factory failed to create connection", e); } - } else if (jndiName != null) - try { - context = new InitialContext(); - DataSource ds = (DataSource) context.lookup(jndiName); - conn = ds.getConnection(); - closeConn = true; - } catch (Throwable e) { - tryClose(context); - throw new RuntimeException("JNDI lookup failed to create connection", e); - } + } } if (conn == null) throw new NullPointerException("Connection needs to be non-null for QueryMapper..."); this.conn = conn; - this.context = context; this.closeConn = closeConn; } @@ -98,7 +74,6 @@ public class QueryMapper implements JdbcMapper { protected QueryMapper() { this.cm = null; this.conn = null; - this.context = null; this.closeConn = false; } @@ -106,7 +81,6 @@ public class QueryMapper implements JdbcMapper { public void close() { if (closeConn) { tryClose(conn); - tryClose(context); } } diff --git a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java index 799cccc..e4b4183 100644 --- a/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java +++ b/querymapper/src/test/java/com/moparisthebest/jdbc/QueryRunnerTest.java @@ -14,7 +14,7 @@ import static com.moparisthebest.jdbc.QueryMapperTest.*; * Created by mopar on 7/1/17. */ public class QueryRunnerTest { - public static final QueryRunner qr = QueryRunner.withRetry(new JdbcMapperFactory(QueryMapper.class, new Factory() { + public static final QueryRunner qr = QueryRunner.withRetry(JdbcMapperFactory.of(QueryMapper.class, new Factory() { @Override public Connection create() throws SQLException { return QueryMapperTest.getConnection();