/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.core;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jdbi.v3.core.rule.H2DatabaseRule;
import org.jdbi.v3.core.statement.DefaultStatementBuilder;
import org.junit.Rule;
import org.junit.Test;
/**
* Oracle was getting angry about too many open cursors because of the large number
* of prepared statements being created and cached indefinitely.
*/
public class TestTooManyCursors
{
@Rule
public H2DatabaseRule dbRule = new H2DatabaseRule();
@Test
public void testFoo() throws Exception
{
ConnectionFactory cf = dbRule.getConnectionFactory();
ConnectionFactory errorCf = new ErrorProducingConnectionFactory(cf, 99);
Jdbi db = Jdbi.create(errorCf);
db.useHandle(handle -> {
handle.setStatementBuilder(new DefaultStatementBuilder());
for (int idx = 0; idx < 100; idx++) {
handle.createQuery("SELECT " + idx + " FROM something").mapTo(int.class).findFirst();
}
});
}
private static class ErrorProducingConnectionFactory implements ConnectionFactory
{
private final ConnectionFactory target;
private final int connCount;
ErrorProducingConnectionFactory(ConnectionFactory target, int i)
{
this.target = target;
connCount = i;
}
@Override
public Connection openConnection() throws SQLException
{
return ConnectionInvocationHandler.newInstance(target.openConnection(), connCount);
}
}
private static class ConnectionInvocationHandler implements InvocationHandler
{
private final Connection connection;
private final int numSuccessfulStatements;
private int numStatements = 0;
public static Connection newInstance(Connection connection, int numSuccessfulStatements)
{
return (Connection) Proxy.newProxyInstance(connection.getClass().getClassLoader(),
new Class[]{Connection.class},
new ConnectionInvocationHandler(connection, numSuccessfulStatements));
}
public ConnectionInvocationHandler(Connection connection, int numSuccessfulStatements)
{
this.connection = connection;
this.numSuccessfulStatements = numSuccessfulStatements;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
try {
if ("createStatement".equals(method.getName()) ||
"prepareCall".equals(method.getName()) ||
"prepareStatement".equals(method.getName())) {
if (++numStatements > numSuccessfulStatements) {
throw new SQLException("Fake 'maximum open cursors exceeded' error");
}
return StatementInvocationHandler.newInstance((Statement) method.invoke(connection, args), this);
}
else {
return method.invoke(connection, args);
}
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
public void registerCloseStatement()
{
numStatements--;
}
}
private static class StatementInvocationHandler implements InvocationHandler
{
private final Statement stmt;
private final ConnectionInvocationHandler connectionHandler;
public static Statement newInstance(Statement stmt, ConnectionInvocationHandler connectionHandler)
{
Class<?> o = stmt.getClass();
List<Class<?>> interfaces = new ArrayList<>();
while (!o.equals(Object.class)) {
interfaces.addAll(Arrays.asList(o.getInterfaces()));
o = o.getSuperclass();
}
return (Statement) Proxy.newProxyInstance(stmt.getClass().getClassLoader(),
interfaces.toArray(new Class[interfaces.size()]),
new StatementInvocationHandler(stmt, connectionHandler));
}
public StatementInvocationHandler(Statement stmt, ConnectionInvocationHandler connectionHandler)
{
this.stmt = stmt;
this.connectionHandler = connectionHandler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if ("close".equals(method.getName())) {
connectionHandler.registerCloseStatement();
}
try {
return method.invoke(stmt, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}