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