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}