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}