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.Set;
029import java.util.logging.Logger;
030
031import org.apache.commons.pool2.ObjectPool;
032
033/**
034 * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
035 *
036 * @since 2.0
037 */
038public class PoolingDriver implements Driver {
039
040    /** Register myself with the {@link DriverManager}. */
041    static {
042        try {
043            DriverManager.registerDriver(new PoolingDriver());
044        } catch (final Exception e) {
045            // ignore
046        }
047    }
048
049    /** The map of registered pools. */
050    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
051
052    /** Controls access to the underlying connection */
053    private final boolean accessToUnderlyingConnectionAllowed;
054
055    /**
056     * Constructs a new driver with <code>accessToUnderlyingConnectionAllowed</code> enabled.
057     */
058    public PoolingDriver() {
059        this(true);
060    }
061
062    /**
063     * For unit testing purposes.
064     *
065     * @param accessToUnderlyingConnectionAllowed
066     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
067     */
068    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
069        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
070    }
071
072    /**
073     * Returns the value of the accessToUnderlyingConnectionAllowed property.
074     *
075     * @return true if access to the underlying is allowed, false otherwise.
076     */
077    protected boolean isAccessToUnderlyingConnectionAllowed() {
078        return accessToUnderlyingConnectionAllowed;
079    }
080
081    /**
082     * Gets the connection pool for the given name.
083     *
084     * @param name
085     *            The pool name
086     * @return The pool
087     * @throws SQLException
088     *             Thrown when the named pool is not registered.
089     */
090    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
091        final ObjectPool<? extends Connection> pool = pools.get(name);
092        if (null == pool) {
093            throw new SQLException("Pool not registered: " + name);
094        }
095        return pool;
096    }
097
098    /**
099     * Registers a named pool.
100     *
101     * @param name
102     *            The pool name.
103     * @param pool
104     *            The pool.
105     */
106    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
107        pools.put(name, pool);
108    }
109
110    /**
111     * Closes a named pool.
112     *
113     * @param name
114     *            The pool name.
115     * @throws SQLException
116     *             Thrown when a problem is caught closing the pool.
117     */
118    public synchronized void closePool(final String name) throws SQLException {
119        @SuppressWarnings("resource")
120        final ObjectPool<? extends Connection> pool = pools.get(name);
121        if (pool != null) {
122            pools.remove(name);
123            try {
124                pool.close();
125            } catch (final Exception e) {
126                throw new SQLException("Error closing pool " + name, e);
127            }
128        }
129    }
130
131    /**
132     * Gets the pool names.
133     *
134     * @return the pool names.
135     */
136    public synchronized String[] getPoolNames() {
137        final Set<String> names = pools.keySet();
138        return names.toArray(new String[names.size()]);
139    }
140
141    @Override
142    public boolean acceptsURL(final String url) throws SQLException {
143        return url == null ? false : url.startsWith(URL_PREFIX);
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 SQLException e) {
158                throw e;
159            } catch (final NoSuchElementException e) {
160                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
161            } catch (final RuntimeException e) {
162                throw e;
163            } catch (final Exception e) {
164                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
165            }
166        }
167        return null;
168    }
169
170    @Override
171    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
172        throw new SQLFeatureNotSupportedException();
173    }
174
175    /**
176     * Invalidates the given connection.
177     *
178     * @param conn
179     *            connection to invalidate
180     * @throws SQLException
181     *             if the connection is not a <code>PoolGuardConnectionWrapper</code> or an error occurs invalidating
182     *             the connection
183     */
184    public void invalidateConnection(final Connection conn) throws SQLException {
185        if (conn instanceof PoolGuardConnectionWrapper) { // normal case
186            final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
187            @SuppressWarnings("unchecked")
188            final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
189            try {
190                pool.invalidateObject(pgconn.getDelegateInternal());
191            } catch (final Exception e) {
192                // Ignore.
193            }
194        } else {
195            throw new SQLException("Invalid connection class");
196        }
197    }
198
199    @Override
200    public int getMajorVersion() {
201        return MAJOR_VERSION;
202    }
203
204    @Override
205    public int getMinorVersion() {
206        return MINOR_VERSION;
207    }
208
209    @Override
210    public boolean jdbcCompliant() {
211        return true;
212    }
213
214    @Override
215    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
216        return new DriverPropertyInfo[0];
217    }
218
219    /** My URL prefix */
220    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
221    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
222
223    // version numbers
224    protected static final int MAJOR_VERSION = 1;
225    protected static final int MINOR_VERSION = 0;
226
227    /**
228     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
229     *
230     * @since 2.0
231     */
232    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
233
234        private final ObjectPool<? extends Connection> pool;
235
236        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
237            super(delegate);
238            this.pool = pool;
239        }
240
241        /**
242         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
243         */
244        @Override
245        public Connection getDelegate() {
246            if (isAccessToUnderlyingConnectionAllowed()) {
247                return super.getDelegate();
248            }
249            return null;
250        }
251
252        /**
253         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
254         */
255        @Override
256        public Connection getInnermostDelegate() {
257            if (isAccessToUnderlyingConnectionAllowed()) {
258                return super.getInnermostDelegate();
259            }
260            return null;
261        }
262    }
263}