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}