1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.tomcat.dbcp.dbcp2;
18
19 import java.io.PrintWriter;
20 import java.sql.Connection;
21 import java.sql.SQLException;
22 import java.sql.SQLFeatureNotSupportedException;
23 import java.util.NoSuchElementException;
24 import java.util.Objects;
25 import java.util.logging.Logger;
26
27 import javax.sql.DataSource;
28
29 import org.apache.juli.logging.Log;
30 import org.apache.juli.logging.LogFactory;
31 import org.apache.tomcat.dbcp.pool2.ObjectPool;
32 import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
33
34 /**
35 * A simple {@link DataSource} implementation that obtains {@link Connection}s from the specified {@link ObjectPool}.
36 *
37 * @param <C>
38 * The connection type
39 *
40 * @since 2.0
41 */
42 public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable {
43
44 private static final Log log = LogFactory.getLog(PoolingDataSource.class);
45
46 /** Controls access to the underlying connection */
47 private boolean accessToUnderlyingConnectionAllowed;
48
49 /**
50 * Constructs a new instance backed by the given connection pool.
51 *
52 * @param pool
53 * the given connection pool.
54 */
55 public PoolingDataSource(final ObjectPool<C> pool) {
56 Objects.requireNonNull(pool, "Pool must not be null.");
57 this.pool = pool;
58 // Verify that pool's factory refers back to it. If not, log a warning and try to fix.
59 if (this.pool instanceof GenericObjectPool<?>) {
60 final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) this.pool)
61 .getFactory();
62 Objects.requireNonNull(pcf, "PoolableConnectionFactory must not be null.");
63 if (pcf.getPool() != this.pool) {
64 log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
65 @SuppressWarnings("unchecked") // PCF must have a pool of PCs
66 final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool;
67 pcf.setPool(p);
68 }
69 }
70 }
71
72 /**
73 * Closes and free all {@link Connection}s from the pool.
74 *
75 * @since 2.1
76 */
77 @Override
78 public void close() throws RuntimeException, SQLException {
79 try {
80 pool.close();
81 } catch (final RuntimeException rte) {
82 throw new RuntimeException(Utils.getMessage("pool.close.fail"), rte);
83 } catch (final Exception e) {
84 throw new SQLException(Utils.getMessage("pool.close.fail"), e);
85 }
86 }
87
88 /**
89 * Returns the value of the accessToUnderlyingConnectionAllowed property.
90 *
91 * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
92 */
93 public boolean isAccessToUnderlyingConnectionAllowed() {
94 return this.accessToUnderlyingConnectionAllowed;
95 }
96
97 /**
98 * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
99 * the underlying connection. (Default: false)
100 *
101 * @param allow
102 * Access to the underlying connection is granted when true.
103 */
104 public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
105 this.accessToUnderlyingConnectionAllowed = allow;
106 }
107
108 /* JDBC_4_ANT_KEY_BEGIN */
109 @Override
110 public boolean isWrapperFor(final Class<?> iface) throws SQLException {
111 return false;
112 }
113
114 @Override
115 public <T> T unwrap(final Class<T> iface) throws SQLException {
116 throw new SQLException("PoolingDataSource is not a wrapper.");
117 }
118 /* JDBC_4_ANT_KEY_END */
119
120 @Override
121 public Logger getParentLogger() throws SQLFeatureNotSupportedException {
122 throw new SQLFeatureNotSupportedException();
123 }
124
125 // --- DataSource methods -----------------------------------------
126
127 /**
128 * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
129 * {@link ObjectPool#borrowObject}.
130 */
131 @Override
132 public Connection getConnection() throws SQLException {
133 try {
134 final C conn = pool.borrowObject();
135 if (conn == null) {
136 return null;
137 }
138 return new PoolGuardConnectionWrapper<>(conn);
139 } catch (final SQLException e) {
140 throw e;
141 } catch (final NoSuchElementException e) {
142 throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
143 } catch (final RuntimeException e) {
144 throw e;
145 } catch (final InterruptedException e) {
146 // Reset the interrupt status so it is visible to callers
147 Thread.currentThread().interrupt();
148 throw new SQLException("Cannot get a connection, general error", e);
149 } catch (final Exception e) {
150 throw new SQLException("Cannot get a connection, general error", e);
151 }
152 }
153
154 /**
155 * Throws {@link UnsupportedOperationException}
156 *
157 * @throws UnsupportedOperationException
158 * always thrown
159 */
160 @Override
161 public Connection getConnection(final String uname, final String passwd) throws SQLException {
162 throw new UnsupportedOperationException();
163 }
164
165 /**
166 * Returns my log writer.
167 *
168 * @return my log writer
169 * @see DataSource#getLogWriter
170 */
171 @Override
172 public PrintWriter getLogWriter() {
173 return logWriter;
174 }
175
176 /**
177 * Throws {@link UnsupportedOperationException}.
178 *
179 * @throws UnsupportedOperationException
180 * As this implementation does not support this feature.
181 */
182 @Override
183 public int getLoginTimeout() {
184 throw new UnsupportedOperationException("Login timeout is not supported.");
185 }
186
187 /**
188 * Throws {@link UnsupportedOperationException}.
189 *
190 * @throws UnsupportedOperationException
191 * As this implementation does not support this feature.
192 */
193 @Override
194 public void setLoginTimeout(final int seconds) {
195 throw new UnsupportedOperationException("Login timeout is not supported.");
196 }
197
198 /**
199 * Sets my log writer.
200 *
201 * @see DataSource#setLogWriter
202 */
203 @Override
204 public void setLogWriter(final PrintWriter out) {
205 logWriter = out;
206 }
207
208 /** My log writer. */
209 private PrintWriter logWriter = null;
210
211 private final ObjectPool<C> pool;
212
213 protected ObjectPool<C> getPool() {
214 return pool;
215 }
216
217 /**
218 * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
219 *
220 * @since 2.0
221 */
222 private class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> {
223
224 PoolGuardConnectionWrapper(final D delegate) {
225 super(delegate);
226 }
227
228 /**
229 * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getDelegate()
230 */
231 @Override
232 public D getDelegate() {
233 return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null;
234 }
235
236 /**
237 * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getInnermostDelegate()
238 */
239 @Override
240 public Connection getInnermostDelegate() {
241 return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null;
242 }
243
244 @Override
245 public void close() throws SQLException {
246 if (getDelegateInternal() != null) {
247 super.close();
248 super.setDelegate(null);
249 }
250 }
251
252 @Override
253 public boolean isClosed() throws SQLException {
254 return getDelegateInternal() == null ? true : super.isClosed();
255 }
256 }
257 }
258