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 * https://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 final 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"); 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).getFactory(); 107 Objects.requireNonNull(pcf, "this.pool.getFactory()"); 108 if (pcf.getPool() != this.pool) { 109 log.warn(Utils.getMessage("poolingDataSource.factoryConfig")); 110 @SuppressWarnings("unchecked") // PCF must have a pool of PCs 111 final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool; 112 pcf.setPool(p); 113 } 114 } 115 } 116 117 /** 118 * Closes and free all {@link Connection}s from the pool. 119 * 120 * @since 2.1 121 */ 122 @Override 123 public void close() throws SQLException { 124 try { 125 pool.close(); 126 } catch (final Exception e) { 127 throw new SQLException(Utils.getMessage("pool.close.fail"), e); 128 } 129 } 130 131 /** 132 * Returns a {@link Connection} from my pool, according to the contract specified by 133 * {@link ObjectPool#borrowObject}. 134 */ 135 @Override 136 public Connection getConnection() throws SQLException { 137 try { 138 final C conn = pool.borrowObject(); 139 if (conn == null) { 140 return null; 141 } 142 return new PoolGuardConnectionWrapper<>(conn); 143 } catch (final NoSuchElementException e) { 144 throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e); 145 } catch (final SQLException | RuntimeException e) { 146 throw e; 147 } catch (final InterruptedException e) { 148 // Reset the interrupt status so it is visible to callers 149 Thread.currentThread().interrupt(); 150 throw new SQLException("Cannot get a connection, general error", e); 151 } catch (final Exception e) { 152 throw new SQLException("Cannot get a connection, general error", e); 153 } 154 } 155 156 /** 157 * Throws {@link UnsupportedOperationException} 158 * 159 * @throws UnsupportedOperationException 160 * always thrown 161 */ 162 @Override 163 public Connection getConnection(final String userName, final String password) throws SQLException { 164 throw new UnsupportedOperationException(); 165 } 166 167 /** 168 * Throws {@link UnsupportedOperationException}. 169 * 170 * @throws UnsupportedOperationException 171 * As this implementation does not support this feature. 172 */ 173 @Override 174 public int getLoginTimeout() { 175 throw new UnsupportedOperationException("Login timeout is not supported."); 176 } 177 178 /** 179 * Returns my log writer. 180 * 181 * @return my log writer 182 * @see DataSource#getLogWriter 183 */ 184 @Override 185 public PrintWriter getLogWriter() { 186 return logWriter; 187 } 188 189 @Override 190 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 191 throw new SQLFeatureNotSupportedException(); 192 } 193 194 /** 195 * Gets the backing object pool. 196 * 197 * @return the backing object pool. 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}