LeftOpenConnectionWarning.java

/*
 *
 * The DbUnit Database Testing Framework
 * Copyright (C)2002-2026, DbUnit.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package org.dbunit.database.connection;

import java.util.function.Consumer;
import java.util.function.Supplier;

import org.dbunit.IDatabaseTester;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.database.IDatabaseConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Logs one warning, the first time it sees a connection that is going to be left open while
 * the {@link JdbcDatabaseTester} that supplied it creates a new connection on every call. A
 * connection is left open for its real owner to close, as
 * {@link ConnectionOwnership#isLeftOpenByConfiguration()} decides: the
 * {@code closeConnectionAfterTest} flag is {@code false}, or an operation listener is the
 * no-op one. A {@link JdbcDatabaseTester} built without a
 * {@link org.dbunit.database.CachingConnectionProvider} has no such owner: nothing else holds
 * the connection it just created, and the next call creates another. A test case used for one
 * test leaves one open after it, and one reused across tests keeps one and leaves another each
 * time a step fails, so a long run can exhaust the database's connections while its tests
 * pass.
 *
 * <p>Only a {@link JdbcDatabaseTester} is judged, through
 * {@link JdbcDatabaseTester#isReusingConnection()}. Another tester, such as a
 * {@link org.dbunit.DataSourceDatabaseTester}, cannot be told apart from one whose data source
 * hands back the same connection, as a transaction-aware one does, so it is left alone.
 *
 * <p>Used as an {@code onAcquired} hook of a {@link TestScopedConnection}, next to
 * {@link AutoCommitOffWarning}, so the check runs where a connection arrives. One instance per
 * lifecycle owner; it warns at most once regardless of how many times the connection is
 * (re)acquired. The tester is checked before the ownership is asked anything, so a tester that
 * is not a {@link JdbcDatabaseTester} sees no extra calls.
 *
 * @author Jeff Jensen
 * @since 3.6.0
 */
public class LeftOpenConnectionWarning implements Consumer<IDatabaseConnection>
{
    private static final Logger log = LoggerFactory.getLogger(LeftOpenConnectionWarning.class);

    private final ConnectionOwnership ownership;
    private final Supplier<IDatabaseTester> tester;

    private boolean isAlreadyWarned;

    /**
     * Creates the warning for one lifecycle owner.
     *
     * @param ownership Decides whether the configuration leaves the connection open.
     * @param tester Supplies the database tester the connection comes from, read fresh each
     *            time as it can change between tests; may supply {@code null}.
     */
    public LeftOpenConnectionWarning(final ConnectionOwnership ownership,
            final Supplier<IDatabaseTester> tester)
    {
        this.ownership = ownership;
        this.tester = tester;
    }

    /**
     * Warns when the connection is going to be left open and its tester creates a new
     * connection on every call, unless a warning has already been logged by this instance.
     *
     * @param connection The connection just acquired; not inspected, as it is the tester and
     *            the ownership that decide.
     */
    @Override
    public void accept(final IDatabaseConnection connection)
    {
        if (isAlreadyWarned)
        {
            return;
        }
        final IDatabaseTester databaseTester = tester.get();
        if (!(databaseTester instanceof JdbcDatabaseTester))
        {
            return;
        }
        final JdbcDatabaseTester jdbcTester = (JdbcDatabaseTester) databaseTester;
        final boolean isReusingConnection = jdbcTester.isReusingConnection();
        if (isReusingConnection)
        {
            return;
        }
        final boolean isLeftOpen = ownership.isLeftOpenByConfiguration();
        if (!isLeftOpen)
        {
            return;
        }

        isAlreadyWarned = true;
        log.warn("The connection this test's dbUnit steps share is left open after the test,"
                + " because closeConnectionAfterTest is false or an operation listener is the"
                + " no-op listener. The database tester, {}, was built without a"
                + " CachingConnectionProvider, so it creates a new connection on every call and"
                + " nothing closes the one left open: a test case used for one test leaves a"
                + " connection open after it, and one reused across tests keeps one and leaves"
                + " another open each time a step fails, so a long run can exhaust the"
                + " database's connections. Build the tester with a CachingConnectionProvider,"
                + " so it hands out one connection that its owner closes when the run ends, or"
                + " let dbUnit close the connection: leave closeConnectionAfterTest true and do"
                + " not use the no-op listener.", jdbcTester.getClass().getName());
    }
}