001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2;
018
019import java.sql.Connection;
020import java.sql.Driver;
021import java.sql.DriverManager;
022import java.sql.DriverPropertyInfo;
023import java.sql.SQLException;
024import java.sql.SQLFeatureNotSupportedException;
025import java.util.HashMap;
026import java.util.NoSuchElementException;
027import java.util.Properties;
028import java.util.logging.Logger;
029
030import org.apache.commons.pool2.ObjectPool;
031
032/**
033 * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
034 *
035 * @since 2.0
036 */
037public class PoolingDriver implements Driver {
038
039    /**
040     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
041     *
042     * @since 2.0
043     */
044    private final class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
045
046        private final ObjectPool<? extends Connection> pool;
047
048        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
049            super(delegate);
050            this.pool = pool;
051        }
052
053        /**
054         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
055         */
056        @Override
057        public Connection getDelegate() {
058            if (isAccessToUnderlyingConnectionAllowed()) {
059                return super.getDelegate();
060            }
061            return null;
062        }
063
064        /**
065         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
066         */
067        @Override
068        public Connection getInnermostDelegate() {
069            if (isAccessToUnderlyingConnectionAllowed()) {
070                return super.getInnermostDelegate();
071            }
072            return null;
073        }
074    }
075
076    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
077
078    /* Register myself with the {@link DriverManager}. */
079    static {
080        try {
081            DriverManager.registerDriver(new PoolingDriver());
082        } catch (final Exception ignored) {
083            // Ignored
084        }
085    }
086
087    /** The map of registered pools. */
088    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
089
090    /** My URL prefix */
091    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
092
093    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
094
095    // version numbers
096    protected static final int MAJOR_VERSION = 1;
097
098    protected static final int MINOR_VERSION = 0;
099
100    /** Controls access to the underlying connection */
101    private final boolean accessToUnderlyingConnectionAllowed;
102
103    /**
104     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
105     */
106    public PoolingDriver() {
107        this(true);
108    }
109
110    /**
111     * For unit testing purposes.
112     *
113     * @param accessToUnderlyingConnectionAllowed
114     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
115     */
116    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
117        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
118    }
119
120    @Override
121    public boolean acceptsURL(final String url) throws SQLException {
122        return url != null && url.startsWith(URL_PREFIX);
123    }
124
125    /**
126     * Closes a named pool.
127     *
128     * @param name
129     *            The pool name.
130     * @throws SQLException
131     *             Thrown when a problem is caught closing the pool.
132     */
133    public synchronized void closePool(final String name) throws SQLException {
134        @SuppressWarnings("resource")
135        final ObjectPool<? extends Connection> pool = pools.get(name);
136        if (pool != null) {
137            pools.remove(name);
138            try {
139                pool.close();
140            } catch (final Exception e) {
141                throw new SQLException("Error closing pool " + name, e);
142            }
143        }
144    }
145
146    @Override
147    public Connection connect(final String url, final Properties info) throws SQLException {
148        if (acceptsURL(url)) {
149            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
150
151            try {
152                final Connection conn = pool.borrowObject();
153                if (conn == null) {
154                    return null;
155                }
156                return new PoolGuardConnectionWrapper(pool, conn);
157            } catch (final NoSuchElementException e) {
158                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
159            } catch (final SQLException | RuntimeException e) {
160                throw e;
161            } catch (final Exception e) {
162                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
163            }
164        }
165        return null;
166    }
167
168    /**
169     * Gets the connection pool for the given name.
170     *
171     * @param name
172     *            The pool name
173     * @return The pool
174     * @throws SQLException
175     *             Thrown when the named pool is not registered.
176     */
177    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
178        final ObjectPool<? extends Connection> pool = pools.get(name);
179        if (null == pool) {
180            throw new SQLException("Pool not registered: " + name);
181        }
182        return pool;
183    }
184
185    @Override
186    public int getMajorVersion() {
187        return MAJOR_VERSION;
188    }
189
190    @Override
191    public int getMinorVersion() {
192        return MINOR_VERSION;
193    }
194
195    @Override
196    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
197        throw new SQLFeatureNotSupportedException();
198    }
199
200    /**
201     * Gets the pool names.
202     *
203     * @return the pool names.
204     */
205    public synchronized String[] getPoolNames() {
206        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
207    }
208
209    @Override
210    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
211        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
212    }
213    /**
214     * Invalidates the given connection.
215     *
216     * @param conn
217     *            connection to invalidate
218     * @throws SQLException
219     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
220     *             the connection
221     */
222    public void invalidateConnection(final Connection conn) throws SQLException {
223        if (!(conn instanceof PoolGuardConnectionWrapper)) {
224            throw new SQLException("Invalid connection class");
225        }
226        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
227        @SuppressWarnings("unchecked")
228        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
229        try {
230            pool.invalidateObject(pgconn.getDelegateInternal());
231        } catch (final Exception ignored) {
232            // Ignored.
233        }
234    }
235
236    /**
237     * Returns the value of the accessToUnderlyingConnectionAllowed property.
238     *
239     * @return true if access to the underlying is allowed, false otherwise.
240     */
241    protected boolean isAccessToUnderlyingConnectionAllowed() {
242        return accessToUnderlyingConnectionAllowed;
243    }
244
245    @Override
246    public boolean jdbcCompliant() {
247        return true;
248    }
249
250    /**
251     * Registers a named pool.
252     *
253     * @param name
254     *            The pool name.
255     * @param pool
256     *            The pool.
257     */
258    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
259        pools.put(name, pool);
260    }
261}