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 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 java.sql.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 /** 169 * Throws {@link UnsupportedOperationException}. 170 * 171 * @throws UnsupportedOperationException 172 * As this implementation does not support this feature. 173 */ 174 @Override 175 public int getLoginTimeout() { 176 throw new UnsupportedOperationException("Login timeout is not supported."); 177 } 178 179 /** 180 * Returns my log writer. 181 * 182 * @return my log writer 183 * @see DataSource#getLogWriter 184 */ 185 @Override 186 public PrintWriter getLogWriter() { 187 return logWriter; 188 } 189 190 @Override 191 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 192 throw new SQLFeatureNotSupportedException(); 193 } 194 195 /** 196 * Gets the backing object pool. 197 * 198 * @return the backing object pool. 199 */ 200 protected ObjectPool<C> getPool() { 201 return pool; 202 } 203 204 /** 205 * Returns the value of the accessToUnderlyingConnectionAllowed property. 206 * 207 * @return true if access to the underlying {@link Connection} is allowed, false otherwise. 208 */ 209 public boolean isAccessToUnderlyingConnectionAllowed() { 210 return this.accessToUnderlyingConnectionAllowed; 211 } 212 213 @Override 214 public boolean isWrapperFor(final Class<?> iface) throws SQLException { 215 return iface != null && iface.isInstance(this); 216 } 217 218 /** 219 * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to 220 * the underlying connection. (Default: false) 221 * 222 * @param allow 223 * Access to the underlying connection is granted when true. 224 */ 225 public void setAccessToUnderlyingConnectionAllowed(final boolean allow) { 226 this.accessToUnderlyingConnectionAllowed = allow; 227 } 228 229 /** 230 * Throws {@link UnsupportedOperationException}. 231 * 232 * @throws UnsupportedOperationException 233 * As this implementation does not support this feature. 234 */ 235 @Override 236 public void setLoginTimeout(final int seconds) { 237 throw new UnsupportedOperationException("Login timeout is not supported."); 238 } 239 240 /** 241 * Sets my log writer. 242 * 243 * @see DataSource#setLogWriter 244 */ 245 @Override 246 public void setLogWriter(final PrintWriter out) { 247 logWriter = out; 248 } 249 250 @Override 251 public <T> T unwrap(final Class<T> iface) throws SQLException { 252 if (isWrapperFor(iface)) { 253 return iface.cast(this); 254 } 255 throw new SQLException(this + " is not a wrapper for " + iface); 256 } 257}