7 changed files with 551 additions and 0 deletions
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
import com.moparisthebest.jdbc.codegen.JdbcMapperFactory; |
||||
|
||||
import java.io.Closeable; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Proxy; |
||||
import java.sql.ResultSet; |
||||
|
||||
import static com.moparisthebest.jdbc.TryClose.tryClose; |
||||
|
||||
/** |
||||
* This provides implementations of Closeable/JdbcMapper interfaces that simply construct a new object, |
||||
* execute the method, and close the object for each method call. Implementations returned never need |
||||
* to be closed and are as thread-safe as the Factory sent in, of which the provided impls are entirely thread-safe. |
||||
* <p> |
||||
* If any of the methods returns an *interface* that extends Closeable, we assume *this* Closeable should not be closed |
||||
* until the returned value is, and it is therefore wrapped with WrappingCloseable before return |
||||
*/ |
||||
public class SingletonCloseable implements InvocationHandler { |
||||
|
||||
//IFJAVA8_START
|
||||
|
||||
public static <T extends AutoCloseable> T of(final Class<T> jdbcMapper) { |
||||
return of(jdbcMapper, JdbcMapperFactory.of(jdbcMapper)); |
||||
} |
||||
|
||||
public static <T extends AutoCloseable> T of(final Class<T> jdbcMapper, final String jndiName) { |
||||
return of(jdbcMapper, JdbcMapperFactory.of(jdbcMapper, jndiName)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends AutoCloseable> T of(final Class<T> jdbcMapper, final Factory<? extends T> factory) { |
||||
return (T) Proxy.newProxyInstance(jdbcMapper.getClassLoader(), |
||||
new Class<?>[]{jdbcMapper}, new SingletonCloseable(factory) |
||||
); |
||||
} |
||||
|
||||
protected final Factory<? extends AutoCloseable> factory; |
||||
|
||||
protected SingletonCloseable(final Factory<? extends AutoCloseable> factory) { |
||||
this.factory = factory; |
||||
} |
||||
|
||||
@Override |
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { |
||||
AutoCloseable jdbcMapper = null; |
||||
try { |
||||
jdbcMapper = factory.create(); |
||||
Object ret = method.invoke(jdbcMapper, args); |
||||
if (ret instanceof AutoCloseable) { |
||||
// wrap jdbcMapper into ret so it gets closed when ret does, can only do this for Interfaces like ResultSet not concrete classes
|
||||
final Class<?> returnType = method.getReturnType(); |
||||
if (returnType.isInterface() && AutoCloseable.class.isAssignableFrom(returnType)) { |
||||
@SuppressWarnings("unchecked") |
||||
final Class<AutoCloseable> returnTypeCloseable = (Class<AutoCloseable>) returnType; |
||||
ret = WrappingCloseable.wrap((AutoCloseable) ret, returnTypeCloseable, jdbcMapper); |
||||
// now that jdbcMapper will be closed by above, don't let it be closed here
|
||||
// if a ClassCastException might happen here we will leak jdbcMapper, but how could it???
|
||||
jdbcMapper = null; |
||||
} |
||||
} |
||||
return ret; |
||||
} finally { |
||||
tryClose(jdbcMapper); |
||||
} |
||||
} |
||||
|
||||
//IFJAVA8_END
|
||||
|
||||
/*IFJAVA6_START |
||||
|
||||
public static <T extends Closeable> T of(final Class<T> jdbcMapper) { |
||||
return of(jdbcMapper, JdbcMapperFactory.of(jdbcMapper)); |
||||
} |
||||
|
||||
public static <T extends Closeable> T of(final Class<T> jdbcMapper, final String jndiName) { |
||||
return of(jdbcMapper, JdbcMapperFactory.of(jdbcMapper, jndiName)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends Closeable> T of(final Class<T> jdbcMapper, final Factory<? extends T> factory) { |
||||
return (T) Proxy.newProxyInstance(jdbcMapper.getClassLoader(), |
||||
new Class<?>[]{jdbcMapper}, new SingletonCloseable(factory) |
||||
); |
||||
} |
||||
|
||||
protected final Factory<? extends Closeable> factory; |
||||
|
||||
protected SingletonCloseable(final Factory<? extends Closeable> factory) { |
||||
this.factory = factory; |
||||
} |
||||
|
||||
@Override |
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { |
||||
Closeable jdbcMapper = null; |
||||
try { |
||||
jdbcMapper = factory.create(); |
||||
Object ret = method.invoke(jdbcMapper, args); |
||||
if (ret instanceof Closeable) { |
||||
// wrap jdbcMapper into ret so it gets closed when ret does, can only do this for Interfaces like ResultSet not concrete classes
|
||||
final Class<?> returnType = method.getReturnType(); |
||||
if (returnType.isInterface() && Closeable.class.isAssignableFrom(returnType)) { |
||||
@SuppressWarnings("unchecked") final Class<Closeable> returnTypeCloseable = (Class<Closeable>) returnType; |
||||
ret = WrappingCloseable.wrap((Closeable) ret, returnTypeCloseable, jdbcMapper); |
||||
// now that jdbcMapper will be closed by above, don't let it be closed here
|
||||
// if a ClassCastException might happen here we will leak jdbcMapper, but how could it???
|
||||
jdbcMapper = null; |
||||
} |
||||
} else if (ret instanceof ResultSet) { |
||||
// wrap jdbcMapper into ret so it gets closed when ret does, can only do this for Interfaces like ResultSet not concrete classes
|
||||
final Class<?> returnType = method.getReturnType(); |
||||
if (returnType.isInterface() && ResultSet.class.isAssignableFrom(returnType)) { |
||||
@SuppressWarnings("unchecked") final Class<ResultSet> returnTypeCloseable = (Class<ResultSet>) returnType; |
||||
ret = WrappingCloseable.wrap((ResultSet) ret, returnTypeCloseable, jdbcMapper); |
||||
// now that jdbcMapper will be closed by above, don't let it be closed here
|
||||
// if a ClassCastException might happen here we will leak jdbcMapper, but how could it???
|
||||
jdbcMapper = null; |
||||
} |
||||
} |
||||
return ret; |
||||
} finally { |
||||
tryClose(jdbcMapper); |
||||
} |
||||
} |
||||
|
||||
IFJAVA6_END*/ |
||||
|
||||
} |
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
import java.io.Closeable; |
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Proxy; |
||||
import java.sql.ResultSet; |
||||
|
||||
import static com.moparisthebest.jdbc.TryClose.tryClose; |
||||
|
||||
/** |
||||
* This wraps a class implementing closeable and also closes a supplied closeable after this class is closed |
||||
* <p> |
||||
* An example use is for holding a Connection open while a returned ResultSet is still held open, and closing the |
||||
* Connection when the ResultSet is closed |
||||
*/ |
||||
public class WrappingCloseable implements InvocationHandler { |
||||
|
||||
//IFJAVA8_START
|
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends AutoCloseable, C extends AutoCloseable> T wrap(final T delegate, final Class<T> iface, final C closeable) { |
||||
return (T) Proxy.newProxyInstance(iface.getClassLoader(), |
||||
new Class<?>[]{iface}, new WrappingCloseable(delegate, closeable) |
||||
); |
||||
} |
||||
|
||||
protected final Object delegate; |
||||
protected final AutoCloseable closeable; |
||||
|
||||
protected WrappingCloseable(final Object delegate, final AutoCloseable closeable) { |
||||
this.delegate = delegate; |
||||
this.closeable = closeable; |
||||
} |
||||
|
||||
//IFJAVA8_END
|
||||
|
||||
/*IFJAVA6_START |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends Closeable, C extends Closeable> T wrap(final T delegate, final Class<T> iface, final C closeable) { |
||||
return (T) Proxy.newProxyInstance(iface.getClassLoader(), |
||||
new Class<?>[]{iface}, new WrappingCloseable(delegate, closeable) |
||||
); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static <T extends ResultSet, C extends Closeable> T wrap(final T delegate, final Class<T> iface, final C closeable) { |
||||
return (T) Proxy.newProxyInstance(iface.getClassLoader(), |
||||
new Class<?>[]{iface}, new WrappingCloseable(delegate, closeable) |
||||
); |
||||
} |
||||
|
||||
protected final Object delegate; |
||||
protected final Closeable closeable; |
||||
|
||||
protected WrappingCloseable(final Object delegate, final Closeable closeable) { |
||||
this.delegate = delegate; |
||||
this.closeable = closeable; |
||||
} |
||||
|
||||
IFJAVA6_END*/ |
||||
|
||||
@Override |
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { |
||||
try { |
||||
return method.invoke(delegate, args); |
||||
} finally { |
||||
// todo: is there a better way of determining close method?
|
||||
if ((args == null || args.length == 0) && "close".equals(method.getName())) { |
||||
tryClose(closeable); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
import java.io.Closeable; |
||||
|
||||
interface Closed extends Closeable { |
||||
|
||||
boolean isClosed(); |
||||
|
||||
String name(); |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
class SimpleCloseable implements Closed { |
||||
|
||||
final String name; |
||||
|
||||
private boolean closed = false; |
||||
|
||||
SimpleCloseable(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isClosed() { |
||||
return closed; |
||||
} |
||||
|
||||
@Override |
||||
public String name() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
closed = true; |
||||
} |
||||
} |
@ -0,0 +1,198 @@
@@ -0,0 +1,198 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import java.io.Closeable; |
||||
import java.sql.SQLException; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
public class SingletonCloseableTest { |
||||
|
||||
@Test |
||||
public void testRegularCloseable() throws Exception { |
||||
final DaoFactory factory = new DaoFactory(); |
||||
assertEquals(0, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
|
||||
{ |
||||
final Dao dao1 = factory.create(); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
|
||||
final Closed closed1 = dao1.getClosed(); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num1", closed1.name()); |
||||
closed1.close(); |
||||
assertTrue(closed1.isClosed()); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
|
||||
final Closed closed2 = dao1.getClosed(); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num2", closed2.name()); |
||||
closed2.close(); |
||||
assertTrue(closed2.isClosed()); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
|
||||
dao1.close(); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
assertTrue(dao1.isClosed()); |
||||
assertEquals(0, factory.numStillOpen); |
||||
} |
||||
|
||||
final Dao dao1 = factory.create(); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
|
||||
final Closed closed1 = dao1.getClosed(); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num1", closed1.name()); |
||||
|
||||
final Closed closed2 = dao1.getClosed(); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num2", closed2.name()); |
||||
|
||||
assertEquals(5, dao1.getInt()); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
|
||||
dao1.close(); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
assertTrue(dao1.isClosed()); |
||||
assertFalse(closed1.isClosed()); |
||||
assertFalse(closed2.isClosed()); |
||||
} |
||||
|
||||
@Test |
||||
public void testSingletonCloseable() throws Exception { |
||||
final DaoFactory factory = new DaoFactory(); |
||||
assertEquals(0, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
final Dao dao1 = SingletonCloseable.of(Dao.class, factory); |
||||
|
||||
{ |
||||
assertEquals(0, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
|
||||
final Closed closed1 = dao1.getClosed(); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num1", closed1.name()); |
||||
closed1.close(); |
||||
assertTrue(closed1.isClosed()); |
||||
assertEquals(1, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
|
||||
final Closed closed2 = dao1.getClosed(); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num1", closed2.name()); |
||||
closed2.close(); |
||||
assertTrue(closed2.isClosed()); |
||||
assertEquals(2, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
|
||||
dao1.close(); |
||||
assertEquals(3, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
assertFalse(dao1.isClosed()); // always appears open even though does not stay open
|
||||
assertEquals(0, factory.numStillOpen); |
||||
} |
||||
|
||||
assertEquals(4, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
|
||||
final Closed closed1 = dao1.getClosed(); |
||||
assertEquals(5, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
assertEquals("num1", closed1.name()); |
||||
|
||||
final Closed closed2 = dao1.getClosed(); |
||||
assertEquals(6, factory.totalOpened); |
||||
assertEquals(2, factory.numStillOpen); |
||||
assertEquals("num1", closed2.name()); |
||||
|
||||
assertEquals(5, dao1.getInt()); |
||||
assertEquals(7, factory.totalOpened); |
||||
assertEquals(2, factory.numStillOpen); |
||||
|
||||
dao1.close(); |
||||
assertEquals(8, factory.totalOpened); |
||||
assertEquals(2, factory.numStillOpen); |
||||
assertFalse(dao1.isClosed()); |
||||
assertEquals(9, factory.totalOpened); |
||||
assertEquals(2, factory.numStillOpen); |
||||
assertFalse(closed1.isClosed()); |
||||
assertFalse(closed2.isClosed()); |
||||
assertEquals(9, factory.totalOpened); |
||||
assertEquals(2, factory.numStillOpen); |
||||
closed1.close(); |
||||
assertEquals(9, factory.totalOpened); |
||||
assertEquals(1, factory.numStillOpen); |
||||
closed2.close(); |
||||
assertEquals(9, factory.totalOpened); |
||||
assertEquals(0, factory.numStillOpen); |
||||
} |
||||
|
||||
class DaoFactory implements Factory<Dao> { |
||||
int totalOpened = 0; |
||||
int numStillOpen = 0; |
||||
|
||||
@Override |
||||
public Dao create() throws SQLException { |
||||
return new DaoImpl(this); |
||||
} |
||||
} |
||||
|
||||
class DaoImpl implements Dao { |
||||
final DaoFactory factory; |
||||
|
||||
private int simpleCloseableCount = 0; |
||||
private boolean closed = false; |
||||
|
||||
DaoImpl(DaoFactory factory) { |
||||
++factory.totalOpened; |
||||
++factory.numStillOpen; |
||||
this.factory = factory; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isClosed() { |
||||
return closed; |
||||
} |
||||
|
||||
@Override |
||||
public int getInt() { |
||||
return 5; |
||||
} |
||||
|
||||
@Override |
||||
public Closed getClosed() { |
||||
return new SimpleCloseable("num" + (++simpleCloseableCount)); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
if (!closed) { |
||||
--factory.numStillOpen; |
||||
closed = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
interface Dao extends Closeable { |
||||
boolean isClosed(); |
||||
|
||||
int getInt(); |
||||
|
||||
Closed getClosed(); |
||||
} |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
package com.moparisthebest.jdbc; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
public class WrappingCloseableTest { |
||||
|
||||
@Test |
||||
public void test() throws IOException { |
||||
final Closed parent = new SimpleCloseable("parent"); |
||||
final Closed child = new SimpleCloseable("child"); |
||||
|
||||
assertFalse(parent.isClosed()); |
||||
assertEquals("parent", parent.name()); |
||||
|
||||
assertFalse(child.isClosed()); |
||||
assertEquals("child", child.name()); |
||||
|
||||
final Closed wrapped = WrappingCloseable.wrap(child, Closed.class, parent); |
||||
// when wrapped is closed, it should close child and then parent
|
||||
|
||||
assertFalse(parent.isClosed()); |
||||
assertEquals("parent", parent.name()); |
||||
|
||||
assertFalse(child.isClosed()); |
||||
assertEquals("child", child.name()); |
||||
|
||||
assertFalse(wrapped.isClosed()); |
||||
assertEquals("child", wrapped.name()); |
||||
|
||||
wrapped.close(); |
||||
|
||||
assertTrue(parent.isClosed()); |
||||
assertEquals("parent", parent.name()); |
||||
|
||||
assertTrue(child.isClosed()); |
||||
assertEquals("child", child.name()); |
||||
|
||||
assertTrue(wrapped.isClosed()); |
||||
assertEquals("child", wrapped.name()); |
||||
} |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package com.moparisthebest.jdbc.codegen; |
||||
|
||||
import com.moparisthebest.jdbc.Factory; |
||||
import com.moparisthebest.jdbc.QueryMapperTest; |
||||
import com.moparisthebest.jdbc.SingletonCloseable; |
||||
import org.junit.Test; |
||||
|
||||
import java.sql.Connection; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
|
||||
import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1; |
||||
import static com.moparisthebest.jdbc.TryClose.tryClose; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
public class PersonDAOSingletonTest { |
||||
|
||||
public static final PersonDAO personDAO = SingletonCloseable.of(PersonDAO.class, JdbcMapperFactory.of(PersonDAO.class, new Factory<Connection>() { |
||||
@Override |
||||
public Connection create() throws SQLException { |
||||
return QueryMapperTest.getConnection(); |
||||
} |
||||
})); |
||||
|
||||
@Test |
||||
public void testRegularPersonDAO() throws Exception { |
||||
PersonDAO personDAO = null; |
||||
try { |
||||
personDAO = JdbcMapperFactory.create(PersonDAO.class, new Factory<Connection>() { |
||||
@Override |
||||
public Connection create() throws SQLException { |
||||
return QueryMapperTest.getConnection(); |
||||
} |
||||
}); |
||||
testPerson(personDAO); |
||||
} finally { |
||||
tryClose(personDAO); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testSingletonPersonDAO() throws Exception { |
||||
testPerson(personDAO); |
||||
} |
||||
|
||||
private void testPerson(final PersonDAO personDAO) throws Exception { |
||||
assertEquals(fieldPerson1, personDAO.getPerson(fieldPerson1.getPersonNo())); |
||||
ResultSet rs = null; |
||||
try { |
||||
rs = personDAO.getPeopleResultSet(fieldPerson1.getLastName()); |
||||
assertTrue(rs.next()); |
||||
assertEquals(fieldPerson1.getLastName(), rs.getString("last_name")); |
||||
} finally { |
||||
tryClose(rs); |
||||
} |
||||
try { |
||||
rs = personDAO.getPeopleResultSetCached(fieldPerson1.getLastName()); |
||||
assertTrue(rs.next()); |
||||
assertEquals(fieldPerson1.getLastName(), rs.getString("last_name")); |
||||
} finally { |
||||
tryClose(rs); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue