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    /**
091     * The Apache Commons connection string prefix {@value}.
092     */
093    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
094
095    /**
096     * The String length of {@link #URL_PREFIX}.
097     */
098    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
099
100    /**
101     * Major version number.
102     */
103    protected static final int MAJOR_VERSION = 1;
104
105    /**
106     * Minor version number.
107     */
108    protected static final int MINOR_VERSION = 0;
109
110    /** Controls access to the underlying connection */
111    private final boolean accessToUnderlyingConnectionAllowed;
112
113    /**
114     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
115     */
116    public PoolingDriver() {
117        this(true);
118    }
119
120    /**
121     * For unit testing purposes.
122     *
123     * @param accessToUnderlyingConnectionAllowed
124     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
125     */
126    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
127        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
128    }
129
130    @Override
131    public boolean acceptsURL(final String url) throws SQLException {
132        return url != null && url.startsWith(URL_PREFIX);
133    }
134
135    /**
136     * Closes a named pool.
137     *
138     * @param name
139     *            The pool name.
140     * @throws SQLException
141     *             Thrown when a problem is caught closing the pool.
142     */
143    public synchronized void closePool(final String name) throws SQLException {
144        @SuppressWarnings("resource")
145        final ObjectPool<? extends Connection> pool = pools.get(name);
146        if (pool != null) {
147            pools.remove(name);
148            try {
149                pool.close();
150            } catch (final Exception e) {
151                throw new SQLException("Error closing pool " + name, e);
152            }
153        }
154    }
155
156    @Override
157    public Connection connect(final String url, final Properties info) throws SQLException {
158        if (acceptsURL(url)) {
159            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
160            try {
161                final Connection conn = pool.borrowObject();
162                if (conn == null) {
163                    return null;
164                }
165                return new PoolGuardConnectionWrapper(pool, conn);
166            } catch (final NoSuchElementException e) {
167                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
168            } catch (final SQLException | RuntimeException e) {
169                throw e;
170            } catch (final Exception e) {
171                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
172            }
173        }
174        return null;
175    }
176
177    /**
178     * Gets the connection pool for the given name.
179     *
180     * @param name
181     *            The pool name
182     * @return The pool
183     * @throws SQLException
184     *             Thrown when the named pool is not registered.
185     */
186    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
187        final ObjectPool<? extends Connection> pool = pools.get(name);
188        if (null == pool) {
189            throw new SQLException("Pool not registered: " + name);
190        }
191        return pool;
192    }
193
194    @Override
195    public int getMajorVersion() {
196        return MAJOR_VERSION;
197    }
198
199    @Override
200    public int getMinorVersion() {
201        return MINOR_VERSION;
202    }
203
204    @Override
205    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
206        throw new SQLFeatureNotSupportedException();
207    }
208
209    /**
210     * Gets the pool names.
211     *
212     * @return the pool names.
213     */
214    public synchronized String[] getPoolNames() {
215        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
216    }
217
218    @Override
219    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
220        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
221    }
222    /**
223     * Invalidates the given connection.
224     *
225     * @param conn
226     *            connection to invalidate
227     * @throws SQLException
228     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
229     *             the connection
230     */
231    public void invalidateConnection(final Connection conn) throws SQLException {
232        if (!(conn instanceof PoolGuardConnectionWrapper)) {
233            throw new SQLException("Invalid connection class");
234        }
235        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
236        @SuppressWarnings("unchecked")
237        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
238        try {
239            pool.invalidateObject(pgconn.getDelegateInternal());
240        } catch (final Exception ignored) {
241            // Ignored.
242        }
243    }
244
245    /**
246     * Returns the value of the accessToUnderlyingConnectionAllowed property.
247     *
248     * @return true if access to the underlying is allowed, false otherwise.
249     */
250    protected boolean isAccessToUnderlyingConnectionAllowed() {
251        return accessToUnderlyingConnectionAllowed;
252    }
253
254    @Override
255    public boolean jdbcCompliant() {
256        return true;
257    }
258
259    /**
260     * Registers a named pool.
261     *
262     * @param name
263     *            The pool name.
264     * @param pool
265     *            The pool.
266     */
267    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
268        pools.put(name, pool);
269    }
270}