1 /*
2  * Copyright (C) 2013 Brett Wooldridge
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.zaxxer.hikari;
18
19 import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
20 import com.zaxxer.hikari.pool.HikariPool;
21 import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import javax.sql.DataSource;
26 import java.io.Closeable;
27 import java.io.PrintWriter;
28 import java.sql.Connection;
29 import java.sql.SQLException;
30 import java.sql.SQLFeatureNotSupportedException;
31 import java.util.concurrent.atomic.AtomicBoolean;
32
33 import static com.zaxxer.hikari.pool.HikariPool.POOL_NORMAL;
34
35 /**
36  * The HikariCP pooled DataSource.
37  *
38  * @author Brett Wooldridge
39  */

40 public class HikariDataSource extends HikariConfig implements DataSource, Closeable
41 {
42    private static final Logger LOGGER = LoggerFactory.getLogger(HikariDataSource.class);
43
44    private final AtomicBoolean isShutdown = new AtomicBoolean();
45
46    private final HikariPool fastPathPool;
47    private volatile HikariPool pool;
48
49    /**
50     * Default constructor.  Setters are used to configure the pool.  Using
51     * this constructor vs. {@link #HikariDataSource(HikariConfig)} will
52     * result in {@link #getConnection()} performance that is slightly lower
53     * due to lazy initialization checks.
54     *
55     * The first call to {@link #getConnection()} starts the pool.  Once the pool
56     * is started, the configuration is "sealed" and no further configuration
57     * changes are possible -- except via {@link HikariConfigMXBean} methods.
58     */

59    public HikariDataSource()
60    {
61       super();
62       fastPathPool = null;
63    }
64
65    /**
66     * Construct a HikariDataSource with the specified configuration.  The
67     * {@link HikariConfig} is copied and the pool is started by invoking this
68     * constructor.
69     *
70     * The {@link HikariConfig} can be modified without affecting the HikariDataSource
71     * and used to initialize another HikariDataSource instance.
72     *
73     * @param configuration a HikariConfig instance
74     */

75    public HikariDataSource(HikariConfig configuration)
76    {
77       configuration.validate();
78       configuration.copyStateTo(this);
79
80       LOGGER.info("{} - Starting...", configuration.getPoolName());
81       pool = fastPathPool = new HikariPool(this);
82       LOGGER.info("{} - Start completed.", configuration.getPoolName());
83
84       this.seal();
85    }
86
87    // ***********************************************************************
88    //                          DataSource methods
89    // ***********************************************************************
90
91    /** {@inheritDoc} */
92    @Override
93    public Connection getConnection() throws SQLException
94    {
95       if (isClosed()) {
96          throw new SQLException("HikariDataSource " + this + " has been closed.");
97       }
98
99       if (fastPathPool != null) {
100          return fastPathPool.getConnection();
101       }
102
103       // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
104       HikariPool result = pool;
105       if (result == null) {
106          synchronized (this) {
107             result = pool;
108             if (result == null) {
109                validate();
110                LOGGER.info("{} - Starting...", getPoolName());
111                try {
112                   pool = result = new HikariPool(this);
113                   this.seal();
114                }
115                catch (PoolInitializationException pie) {
116                   if (pie.getCause() instanceof SQLException) {
117                      throw (SQLException) pie.getCause();
118                   }
119                   else {
120                      throw pie;
121                   }
122                }
123                LOGGER.info("{} - Start completed.", getPoolName());
124             }
125          }
126       }
127
128       return result.getConnection();
129    }
130
131    /** {@inheritDoc} */
132    @Override
133    public Connection getConnection(String username, String password) throws SQLException
134    {
135       throw new SQLFeatureNotSupportedException();
136    }
137
138    /** {@inheritDoc} */
139    @Override
140    public PrintWriter getLogWriter() throws SQLException
141    {
142       HikariPool p = pool;
143       return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null);
144    }
145
146    /** {@inheritDoc} */
147    @Override
148    public void setLogWriter(PrintWriter out) throws SQLException
149    {
150       HikariPool p = pool;
151       if (p != null) {
152          p.getUnwrappedDataSource().setLogWriter(out);
153       }
154    }
155
156    /** {@inheritDoc} */
157    @Override
158    public void setLoginTimeout(int seconds) throws SQLException
159    {
160       HikariPool p = pool;
161       if (p != null) {
162          p.getUnwrappedDataSource().setLoginTimeout(seconds);
163       }
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    public int getLoginTimeout() throws SQLException
169    {
170       HikariPool p = pool;
171       return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0);
172    }
173
174    /** {@inheritDoc} */
175    @Override
176    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
177    {
178       throw new SQLFeatureNotSupportedException();
179    }
180
181    /** {@inheritDoc} */
182    @Override
183    @SuppressWarnings("unchecked")
184    public <T> T unwrap(Class<T> iface) throws SQLException
185    {
186       if (iface.isInstance(this)) {
187          return (T) this;
188       }
189
190       HikariPool p = pool;
191       if (p != null) {
192          final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
193          if (iface.isInstance(unwrappedDataSource)) {
194             return (T) unwrappedDataSource;
195          }
196
197          if (unwrappedDataSource != null) {
198             return unwrappedDataSource.unwrap(iface);
199          }
200       }
201
202       throw new SQLException("Wrapped DataSource is not an instance of " + iface);
203    }
204
205    /** {@inheritDoc} */
206    @Override
207    public boolean isWrapperFor(Class<?> iface) throws SQLException
208    {
209       if (iface.isInstance(this)) {
210          return true;
211       }
212
213       HikariPool p = pool;
214       if (p != null) {
215          final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
216          if (iface.isInstance(unwrappedDataSource)) {
217             return true;
218          }
219
220          if (unwrappedDataSource != null) {
221             return unwrappedDataSource.isWrapperFor(iface);
222          }
223       }
224
225       return false;
226    }
227
228    // ***********************************************************************
229    //                        HikariConfigMXBean methods
230    // ***********************************************************************
231
232    /** {@inheritDoc} */
233    @Override
234    public void setMetricRegistry(Object metricRegistry)
235    {
236       boolean isAlreadySet = getMetricRegistry() != null;
237       super.setMetricRegistry(metricRegistry);
238
239       HikariPool p = pool;
240       if (p != null) {
241          if (isAlreadySet) {
242             throw new IllegalStateException("MetricRegistry can only be set one time");
243          }
244          else {
245             p.setMetricRegistry(super.getMetricRegistry());
246          }
247       }
248    }
249
250    /** {@inheritDoc} */
251    @Override
252    public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory)
253    {
254       boolean isAlreadySet = getMetricsTrackerFactory() != null;
255       super.setMetricsTrackerFactory(metricsTrackerFactory);
256
257       HikariPool p = pool;
258       if (p != null) {
259          if (isAlreadySet) {
260             throw new IllegalStateException("MetricsTrackerFactory can only be set one time");
261          }
262          else {
263             p.setMetricsTrackerFactory(super.getMetricsTrackerFactory());
264          }
265       }
266    }
267
268    /** {@inheritDoc} */
269    @Override
270    public void setHealthCheckRegistry(Object healthCheckRegistry)
271    {
272       boolean isAlreadySet = getHealthCheckRegistry() != null;
273       super.setHealthCheckRegistry(healthCheckRegistry);
274
275       HikariPool p = pool;
276       if (p != null) {
277          if (isAlreadySet) {
278             throw new IllegalStateException("HealthCheckRegistry can only be set one time");
279          }
280          else {
281             p.setHealthCheckRegistry(super.getHealthCheckRegistry());
282          }
283       }
284    }
285
286    // ***********************************************************************
287    //                        HikariCP-specific methods
288    // ***********************************************************************
289
290    /**
291     * Returns {@code trueif the pool as been started and is not suspended or shutdown.
292     *
293     * @return {@code trueif the pool as been started and is not suspended or shutdown.
294     */

295    public boolean isRunning()
296    {
297       return pool != null && pool.poolState == POOL_NORMAL;
298    }
299
300    /**
301     * Get the {@code HikariPoolMXBean} for this HikariDataSource instance.  If this method is called on
302     * a {@code HikariDataSource} that has been constructed without a {@code HikariConfig} instance,
303     * and before an initial call to {@code #getConnection()}, the return value will be {@code null}.
304     *
305     * @return the {@code HikariPoolMXBean} instance, or {@code null}.
306     */

307    public HikariPoolMXBean getHikariPoolMXBean()
308    {
309       return pool;
310    }
311
312    /**
313     * Get the {@code HikariConfigMXBean} for this HikariDataSource instance.
314     *
315     * @return the {@code HikariConfigMXBean} instance.
316     */

317    public HikariConfigMXBean getHikariConfigMXBean()
318    {
319       return this;
320    }
321
322    /**
323     * Evict a connection from the pool.  If the connection has already been closed (returned to the pool)
324     * this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
325     * currently in use.  If the connection has not been closed, the eviction is immediate.
326     *
327     * @param connection the connection to evict from the pool
328     */

329    public void evictConnection(Connection connection)
330    {
331       HikariPool p;
332       if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
333          p.evictConnection(connection);
334       }
335    }
336
337    /**
338     * Shutdown the DataSource and its associated pool.
339     */

340    @Override
341    public void close()
342    {
343       if (isShutdown.getAndSet(true)) {
344          return;
345       }
346
347       HikariPool p = pool;
348       if (p != null) {
349          try {
350             LOGGER.info("{} - Shutdown initiated...", getPoolName());
351             p.shutdown();
352             LOGGER.info("{} - Shutdown completed.", getPoolName());
353          }
354          catch (InterruptedException e) {
355             LOGGER.warn("{} - Interrupted during closing", getPoolName(), e);
356             Thread.currentThread().interrupt();
357          }
358       }
359    }
360
361    /**
362     * Determine whether the HikariDataSource has been closed.
363     *
364     * @return true if the HikariDataSource has been closed, false otherwise
365     */

366    public boolean isClosed()
367    {
368       return isShutdown.get();
369    }
370
371    /** {@inheritDoc} */
372    @Override
373    public String toString()
374    {
375       return "HikariDataSource (" + pool + ")";
376    }
377 }
378