1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 package org.beiter.michael.db;
34
35 import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
36 import org.apache.commons.dbcp2.PoolableConnection;
37 import org.apache.commons.dbcp2.PoolableConnectionFactory;
38 import org.apache.commons.dbcp2.PoolingDataSource;
39 import org.apache.commons.lang3.Validate;
40 import org.apache.commons.pool2.impl.GenericObjectPool;
41 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import javax.naming.Context;
46 import javax.naming.InitialContext;
47 import javax.naming.NamingException;
48 import javax.sql.DataSource;
49 import java.sql.Connection;
50 import java.sql.SQLException;
51 import java.util.HashMap;
52 import java.util.Map;
53 import java.util.Properties;
54 import java.util.concurrent.ConcurrentHashMap;
55
56
57
58
59
60
61
62
63 public final class ConnectionFactory {
64
65
66
67
68 private static final Logger LOG = LoggerFactory.getLogger(ConnectionFactory.class);
69
70
71
72
73
74 private static final ConcurrentHashMap<String, PoolingDataSource<PoolableConnection>> CONNECTION_POOLS =
75 new ConcurrentHashMap<>();
76
77
78
79
80 private ConnectionFactory() {
81 }
82
83
84
85
86
87
88
89
90 public static Connection getConnection(final String jndiName)
91 throws FactoryException {
92
93 Validate.notBlank(jndiName);
94
95 try {
96
97 final Context context = new InitialContext();
98
99
100 final Object namedObject = context.lookup(jndiName);
101 if (DataSource.class.isInstance(namedObject)) {
102 final DataSource dataSource = (DataSource) context.lookup(jndiName);
103 context.close();
104
105 return dataSource.getConnection();
106 } else {
107 final String error = "The JNDI name '" + jndiName + "' does not reference a SQL DataSource."
108 + " This is a configuration issue.";
109 LOG.warn(error);
110 throw new FactoryException(error);
111 }
112 } catch (SQLException | NamingException e) {
113 final String error = "Error retrieving JDBC connection from JNDI: " + jndiName;
114 LOG.warn(error);
115 throw new FactoryException(error, e);
116 }
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131 public static Connection getConnection(final String driver, final ConnectionSpec connectionSpec,
132 final ConnectionPoolSpec poolSpec)
133 throws FactoryException {
134
135 Validate.notBlank(driver);
136 Validate.notNull(connectionSpec);
137 Validate.notNull(poolSpec);
138
139
140
141 final HashMap<String, String> properties = new HashMap<>();
142 properties.put("user", connectionSpec.getUser());
143 properties.put("password", connectionSpec.getPassword());
144
145 return getConnection(driver, connectionSpec.getUrl(), properties, poolSpec);
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public static Connection getConnection(final String driver, final String url, final Map<String, String> properties,
165 final ConnectionPoolSpec poolSpec)
166 throws FactoryException {
167
168 Validate.notBlank(driver);
169 Validate.notBlank(url);
170 Validate.notNull(properties);
171 Validate.notNull(poolSpec);
172
173
174
175
176 loadDriver(driver);
177
178
179
180 final String username = properties.get("user") == null ? "" : properties.get("user");
181
182
183
184
185 final String key = String.format("%s:%s", url, username);
186
187
188 if (!CONNECTION_POOLS.containsKey(key)) {
189 synchronized (ConnectionFactory.class) {
190 if (!CONNECTION_POOLS.containsKey(key)) {
191
192
193
194
195
196
197
198
199 CONNECTION_POOLS.putIfAbsent(key, getPoolingDataSource(url, properties, poolSpec));
200 }
201 }
202 }
203
204
205
206
207
208
209 try {
210 return CONNECTION_POOLS.get(key).getConnection();
211 } catch (SQLException e) {
212 final String error = "Error retrieving JDBC connection from pool: " + key;
213 LOG.warn(error);
214 throw new FactoryException(error, e);
215 }
216 }
217
218
219
220
221
222
223
224
225 public static void reset() {
226
227
228 CONNECTION_POOLS.clear();
229 }
230
231
232
233
234
235
236
237 private static void loadDriver(final String driver) throws FactoryException {
238
239
240 assert driver != null : "The driver cannot be null";
241
242 LOG.debug("Loading the database driver '" + driver + "'");
243
244
245 try {
246 Class.forName(driver);
247 } catch (ClassNotFoundException e) {
248 final String error = "Error loading JDBC driver class: " + driver;
249 LOG.warn(error, e);
250 throw new FactoryException(error, e);
251 }
252 }
253
254
255
256
257
258
259
260
261
262
263 private static PoolingDataSource<PoolableConnection> getPoolingDataSource(final String url,
264 final Map<String, String> properties,
265 final ConnectionPoolSpec poolSpec) {
266
267
268 assert url != null : "The url cannot be null";
269 assert properties != null : "The properties cannot be null";
270 assert poolSpec != null : "The pol spec cannot be null";
271
272 LOG.debug("Creating new pooled data source for '" + url + "'");
273
274
275 final Properties props = new Properties();
276 props.putAll(properties);
277
278
279 final GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
280 poolConfig.setMaxTotal(poolSpec.getMaxTotal());
281 poolConfig.setMaxIdle(poolSpec.getMaxIdle());
282 poolConfig.setMinIdle(poolSpec.getMinIdle());
283 poolConfig.setMaxWaitMillis(poolSpec.getMaxWaitMillis());
284 poolConfig.setTestOnCreate(poolSpec.isTestOnCreate());
285 poolConfig.setTestOnBorrow(poolSpec.isTestOnBorrow());
286 poolConfig.setTestOnReturn(poolSpec.isTestOnReturn());
287 poolConfig.setTestWhileIdle(poolSpec.isTestWhileIdle());
288 poolConfig.setTimeBetweenEvictionRunsMillis(poolSpec.getTimeBetweenEvictionRunsMillis());
289 poolConfig.setNumTestsPerEvictionRun(poolSpec.getNumTestsPerEvictionRun());
290 poolConfig.setMinEvictableIdleTimeMillis(poolSpec.getMinEvictableIdleTimeMillis());
291 poolConfig.setSoftMinEvictableIdleTimeMillis(poolSpec.getSoftMinEvictableIdleTimeMillis());
292 poolConfig.setLifo(poolSpec.isLifo());
293
294
295
296 final org.apache.commons.dbcp2.ConnectionFactory connFactory = new DriverManagerConnectionFactory(url, props);
297 final PoolableConnectionFactory poolConnFactory = new PoolableConnectionFactory(connFactory, null);
298 poolConnFactory.setDefaultAutoCommit(poolSpec.isDefaultAutoCommit());
299 poolConnFactory.setDefaultReadOnly(poolSpec.isDefaultReadOnly());
300 poolConnFactory.setDefaultTransactionIsolation(poolSpec.getDefaultTransactionIsolation());
301 poolConnFactory.setCacheState(poolSpec.isCacheState());
302 poolConnFactory.setValidationQuery(poolSpec.getValidationQuery());
303 poolConnFactory.setMaxConnLifetimeMillis(poolSpec.getMaxConnLifetimeMillis());
304 final GenericObjectPool<PoolableConnection> connPool = new GenericObjectPool<>(poolConnFactory, poolConfig);
305 poolConnFactory.setPool(connPool);
306
307
308 return new PoolingDataSource<>(connPool);
309 }
310 }