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