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 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 SQLException { 125 try { 126 pool.close(); 127 } catch (final Exception e) { 128 throw new SQLException(Utils.getMessage("pool.close.fail"), e); 129 } 130 } 131 132 /** 133 * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by 134 * {@link ObjectPool#borrowObject}. 135 */ 136 @Override 137 public Connection getConnection() throws SQLException { 138 try { 139 final C conn = pool.borrowObject(); 140 if (conn == null) { 141 return null; 142 } 143 return new PoolGuardConnectionWrapper<>(conn); 144 } catch (final NoSuchElementException e) { 145 throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e); 146 } catch (final SQLException | RuntimeException e) { 147 throw e; 148 } catch (final InterruptedException e) { 149 // Reset the interrupt status so it is visible to callers 150 Thread.currentThread().interrupt(); 151 throw new SQLException("Cannot get a connection, general error", e); 152 } catch (final Exception e) { 153 throw new SQLException("Cannot get a connection, general error", e); 154 } 155 } 156 157 /** 158 * Throws {@link UnsupportedOperationException} 159 * 160 * @throws UnsupportedOperationException 161 * always thrown 162 */ 163 @Override 164 public Connection getConnection(final String userName, final String password) throws SQLException { 165 throw new UnsupportedOperationException(); 166 } 167 168 // --- DataSource methods ----------------------------------------- 169 170 /** 171 * Throws {@link UnsupportedOperationException}. 172 * 173 * @throws UnsupportedOperationException 174 * As this implementation does not support this feature. 175 */ 176 @Override 177 public int getLoginTimeout() { 178 throw new UnsupportedOperationException("Login timeout is not supported."); 179 } 180 181 /** 182 * Returns my log writer. 183 * 184 * @return my log writer 185 * @see DataSource#getLogWriter 186 */ 187 @Override 188 public PrintWriter getLogWriter() { 189 return logWriter; 190 } 191 192 @Override 193 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 194 throw new SQLFeatureNotSupportedException(); 195 } 196 197 protected ObjectPool<C> getPool() { 198 return pool; 199 } 200 201 /** 202 * Returns the value of the accessToUnderlyingConnectionAllowed property. 203 * 204 * @return true if access to the underlying {@link Connection} is allowed, false otherwise. 205 */ 206 public boolean isAccessToUnderlyingConnectionAllowed() { 207 return this.accessToUnderlyingConnectionAllowed; 208 } 209 210 @Override 211 public boolean isWrapperFor(final Class<?> iface) throws SQLException { 212 return iface != null && iface.isInstance(this); 213 } 214 215 /** 216 * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to 217 * the underlying connection. (Default: false) 218 * 219 * @param allow 220 * Access to the underlying connection is granted when true. 221 */ 222 public void setAccessToUnderlyingConnectionAllowed(final boolean allow) { 223 this.accessToUnderlyingConnectionAllowed = allow; 224 } 225 226 /** 227 * Throws {@link UnsupportedOperationException}. 228 * 229 * @throws UnsupportedOperationException 230 * As this implementation does not support this feature. 231 */ 232 @Override 233 public void setLoginTimeout(final int seconds) { 234 throw new UnsupportedOperationException("Login timeout is not supported."); 235 } 236 237 /** 238 * Sets my log writer. 239 * 240 * @see DataSource#setLogWriter 241 */ 242 @Override 243 public void setLogWriter(final PrintWriter out) { 244 logWriter = out; 245 } 246 247 @Override 248 public <T> T unwrap(final Class<T> iface) throws SQLException { 249 if (isWrapperFor(iface)) { 250 return iface.cast(this); 251 } 252 throw new SQLException(this + " is not a wrapper for " + iface); 253 } 254}