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}