Add SingletonCloseable.java and many tests for it

This commit is contained in:
Travis Burtrum 2019-07-19 01:07:16 -04:00
parent 14d4b897ad
commit b62dc1912a
7 changed files with 551 additions and 0 deletions

View File

@ -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*/
}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,10 @@
package com.moparisthebest.jdbc;
import java.io.Closeable;
interface Closed extends Closeable {
boolean isClosed();
String name();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}