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.io.PrintWriter; 020import java.sql.Connection; 021import java.sql.SQLException; 022import java.sql.SQLFeatureNotSupportedException; 023import java.util.NoSuchElementException; 024import java.util.Objects; 025import java.util.logging.Logger; 026 027import javax.sql.DataSource; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.apache.commons.pool2.ObjectPool; 032import org.apache.commons.pool2.impl.GenericObjectPool; 033 034/** 035 * A simple {@link DataSource} implementation that obtains {@link Connection}s from the specified {@link ObjectPool}. 036 * 037 * @param <C> 038 * The connection type 039 * 040 * @since 2.0 041 */ 042public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable { 043 044 /** 045 * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore. 046 * 047 * @since 2.0 048 */ 049 private class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> { 050 051 PoolGuardConnectionWrapper(final D delegate) { 052 super(delegate); 053 } 054 055 @Override 056 public void close() throws SQLException { 057 if (getDelegateInternal() != null) { 058 super.close(); 059 super.setDelegate(null); 060 } 061 } 062 063 /** 064 * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate() 065 */ 066 @Override 067 public D getDelegate() { 068 return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null; 069 } 070 071 /** 072 * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate() 073 */ 074 @Override 075 public Connection getInnermostDelegate() { 076 return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null; 077 } 078 079 @Override 080 public boolean isClosed() throws SQLException { 081 return getDelegateInternal() == null || super.isClosed(); 082 } 083 } 084 085 private static final Log log = LogFactory.getLog(PoolingDataSource.class); 086 087 /** Controls access to the underlying connection */ 088 private boolean accessToUnderlyingConnectionAllowed; 089 090 /** My log writer. */ 091 private PrintWriter logWriter; 092 093 private final ObjectPool<C> pool; 094 095 /** 096 * Constructs a new instance backed by the given connection pool. 097 * 098 * @param pool 099 * the given connection pool. 100 */ 101 public PoolingDataSource(final ObjectPool<C> pool) { 102 Objects.requireNonNull(pool, "Pool must not be null."); 103 this.pool = pool; 104 // Verify that pool's factory refers back to it. If not, log a warning and try to fix. 105 if (this.pool instanceof GenericObjectPool<?>) { 106 final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) this.pool) 107 .getFactory(); 108 Objects.requireNonNull(pcf, "PoolableConnectionFactory must not be null."); 109 if (pcf.getPool() != this.pool) { 110 log.warn(Utils.getMessage("poolingDataSource.factoryConfig")); 111 @SuppressWarnings("unchecked") // PCF must have a pool of PCs 112 final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool; 113 pcf.setPool(p); 114 } 115 } 116 } 117 118 /** 119 * Closes and free all {@link Connection}s from the pool. 120 * 121 * @since 2.1 122 */ 123 @Override 124 public void close() throws RuntimeException, SQLException { 125 try { 126 pool.close(); 127 } catch (final RuntimeException rte) { 128 throw new RuntimeException(Utils.getMessage("pool.close.fail"), rte); 129 } catch (final Exception e) { 130 throw new SQLException(Utils.getMessage("pool.close.fail"), e); 131 } 132 } 133 134 /** 135 * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by 136 * {@link ObjectPool#borrowObject}. 137 */ 138 @Override 139 public Connection getConnection() throws SQLException { 140 try { 141 final C conn = pool.borrowObject(); 142 if (conn == null) { 143 return null; 144 } 145 return new PoolGuardConnectionWrapper<>(conn); 146 } catch (final NoSuchElementException e) { 147 throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e); 148 } catch (final SQLException | RuntimeException e) { 149 throw e; 150 } catch (final InterruptedException e) { 151 // Reset the interrupt status so it is visible to callers 152 Thread.currentThread().interrupt(); 153 throw new SQLException("Cannot get a connection, general error", e); 154 } catch (final Exception e) { 155 throw new SQLException("Cannot get a connection, general error", e); 156 } 157 } 158 159 /** 160 * Throws {@link UnsupportedOperationException} 161 * 162 * @throws UnsupportedOperationException 163 * always thrown 164 */ 165 @Override 166 public Connection getConnection(final String uname, final String passwd) throws SQLException { 167 throw new UnsupportedOperationException(); 168 } 169 170 // --- DataSource methods ----------------------------------------- 171 172 /** 173 * Throws {@link UnsupportedOperationException}. 174 * 175 * @throws UnsupportedOperationException 176 * As this implementation does not support this feature. 177 */ 178 @Override 179 public int getLoginTimeout() { 180 throw new UnsupportedOperationException("Login timeout is not supported."); 181 } 182 183 /** 184 * Returns my log writer. 185 * 186 * @return my log writer 187 * @see DataSource#getLogWriter 188 */ 189 @Override 190 public PrintWriter getLogWriter() { 191 return logWriter; 192 } 193 194 @Override 195 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 196 throw new SQLFeatureNotSupportedException(); 197 } 198 199 protected ObjectPool<C> getPool() { 200 return pool; 201 } 202 203 /** 204 * Returns the value of the accessToUnderlyingConnectionAllowed property. 205 * 206 * @return true if access to the underlying {@link Connection} is allowed, false otherwise. 207 */ 208 public boolean isAccessToUnderlyingConnectionAllowed() { 209 return this.accessToUnderlyingConnectionAllowed; 210 } 211 212 @Override 213 public boolean isWrapperFor(final Class<?> iface) throws SQLException { 214 return iface != null && iface.isInstance(this); 215 } 216 217 /** 218 * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to 219 * the underlying connection. (Default: false) 220 * 221 * @param allow 222 * Access to the underlying connection is granted when true. 223 */ 224 public void setAccessToUnderlyingConnectionAllowed(final boolean allow) { 225 this.accessToUnderlyingConnectionAllowed = allow; 226 } 227 228 /** 229 * Throws {@link UnsupportedOperationException}. 230 * 231 * @throws UnsupportedOperationException 232 * As this implementation does not support this feature. 233 */ 234 @Override 235 public void setLoginTimeout(final int seconds) { 236 throw new UnsupportedOperationException("Login timeout is not supported."); 237 } 238 239 /** 240 * Sets my log writer. 241 * 242 * @see DataSource#setLogWriter 243 */ 244 @Override 245 public void setLogWriter(final PrintWriter out) { 246 logWriter = out; 247 } 248 249 @Override 250 public <T> T unwrap(final Class<T> iface) throws SQLException { 251 if (isWrapperFor(iface)) { 252 return iface.cast(this); 253 } 254 throw new SQLException(this + " is not a wrapper for " + iface); 255 } 256 /* JDBC_4_ANT_KEY_END */ 257}