001package org.avaje.datasource;
002
003import java.sql.Connection;
004import java.util.LinkedHashMap;
005import java.util.Map;
006import java.util.Properties;
007
008/**
009 * Configuration information for a DataSource.
010 */
011public class DataSourceConfig {
012
013  private String url;
014
015  private String username;
016
017  private String password;
018
019  private String driver;
020
021  private int minConnections = 2;
022
023  private int maxConnections = 100;
024
025  private int isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
026
027  private boolean autoCommit;
028  
029  private String heartbeatSql;
030  
031  private int heartbeatFreqSecs = 30;
032  
033  private int heartbeatTimeoutSeconds = 3;
034  
035  private boolean captureStackTrace;
036
037  private int maxStackTraceSize = 5;
038
039  private int leakTimeMinutes = 30;
040
041  private int maxInactiveTimeSecs = 720;
042  
043  private int maxAgeMinutes = 0;
044  
045  private int trimPoolFreqSecs = 59;
046
047  private int pstmtCacheSize = 20;
048  
049  private int cstmtCacheSize = 20;
050
051  private int waitTimeoutMillis = 1000;
052  
053  private String poolListener;
054
055  private boolean offline;
056  
057  private Map<String, String> customProperties;
058
059  private DataSourceAlert alert;
060
061  private DataSourcePoolListener listener;
062
063  /**
064   * Return the connection URL.
065   */
066  public String getUrl() {
067    return url;
068  }
069
070  /**
071   * Set the connection URL.
072   */
073  public void setUrl(String url) {
074    this.url = url;
075  }
076
077  /**
078   * Return the database username.
079   */
080  public String getUsername() {
081    return username;
082  }
083
084  /**
085   * Set the database username.
086   */
087  public void setUsername(String username) {
088    this.username = username;
089  }
090
091  /**
092   * Return the database password.
093   */
094  public String getPassword() {
095    return password;
096  }
097
098  /**
099   * Set the database password.
100   */
101  public void setPassword(String password) {
102    this.password = password;
103  }
104
105  /**
106   * Return the database driver.
107   */
108  public String getDriver() {
109    return driver;
110  }
111
112  /**
113   * Set the database driver.
114   */
115  public void setDriver(String driver) {
116    this.driver = driver;
117  }
118
119  /**
120   * Return the transaction isolation level.
121   */
122  public int getIsolationLevel() {
123    return isolationLevel;
124  }
125
126  /**
127   * Set the transaction isolation level.
128   */
129  public void setIsolationLevel(int isolationLevel) {
130    this.isolationLevel = isolationLevel;
131  }
132  
133  /**
134   * Return autoCommit setting.
135   */
136  public boolean isAutoCommit() {
137    return autoCommit;
138  }
139
140  /**
141   * Set to true to turn on autoCommit.
142   */
143  public void setAutoCommit(boolean autoCommit) {
144    this.autoCommit = autoCommit;
145  }
146
147  /**
148   * Return the minimum number of connections the pool should maintain.
149   */
150  public int getMinConnections() {
151    return minConnections;
152  }
153
154  /**
155   * Set the minimum number of connections the pool should maintain.
156   */
157  public void setMinConnections(int minConnections) {
158    this.minConnections = minConnections;
159  }
160
161  /**
162   * Return the maximum number of connections the pool can reach.
163   */
164  public int getMaxConnections() {
165    return maxConnections;
166  }
167
168  /**
169   * Set the maximum number of connections the pool can reach.
170   */
171  public void setMaxConnections(int maxConnections) {
172    this.maxConnections = maxConnections;
173  }
174
175  /**
176   * Return the alert implementation to use.
177   */
178  public DataSourceAlert getAlert() {
179    return alert;
180  }
181
182  /**
183   * Set the alert implementation to use.
184   */
185  public void setAlert(DataSourceAlert alert) {
186    this.alert = alert;
187  }
188
189  /**
190   * Return the listener to use.
191   */
192  public DataSourcePoolListener getListener() {
193    return listener;
194  }
195
196  /**
197   * Set the listener to use.
198   */
199  public void setListener(DataSourcePoolListener listener) {
200    this.listener = listener;
201  }
202
203  /**
204   * Return a SQL statement used to test the database is accessible.
205   * <p>
206   * Note that if this is not set then it can get defaulted from the
207   * DatabasePlatform.
208   * </p>
209   */
210  public String getHeartbeatSql() {
211    return heartbeatSql;
212  }
213
214  /**
215   * Set a SQL statement used to test the database is accessible.
216   * <p>
217   * Note that if this is not set then it can get defaulted from the
218   * DatabasePlatform.
219   * </p>
220   */
221  public void setHeartbeatSql(String heartbeatSql) {
222    this.heartbeatSql = heartbeatSql;
223  }
224
225  /**
226   * Return the heartbeat frequency in seconds.
227   * <p>
228   * This is the expected frequency in which the DataSource should be checked to
229   * make sure it is healthy and trim idle connections.
230   * </p>
231   */
232  public int getHeartbeatFreqSecs() {
233    return heartbeatFreqSecs;
234  }
235
236  /**
237   * Set the expected heartbeat frequency in seconds.
238   */
239  public void setHeartbeatFreqSecs(int heartbeatFreqSecs) {
240    this.heartbeatFreqSecs = heartbeatFreqSecs;
241  }
242  
243  /**
244   * Return the heart beat timeout in seconds.
245   */
246  public int getHeartbeatTimeoutSeconds() {
247    return heartbeatTimeoutSeconds;
248  }
249
250  /**
251   * Set the heart beat timeout in seconds.
252   */
253  public void setHeartbeatTimeoutSeconds(int heartbeatTimeoutSeconds) {
254    this.heartbeatTimeoutSeconds = heartbeatTimeoutSeconds;
255  }
256
257  /**
258   * Return true if a stack trace should be captured when obtaining a connection
259   * from the pool.
260   * <p>
261   * This can be used to diagnose a suspected connection pool leak.
262   * </p>
263   * <p>
264   * Obviously this has a performance overhead.
265   * </p>
266   */
267  public boolean isCaptureStackTrace() {
268    return captureStackTrace;
269  }
270
271  /**
272   * Set to true if a stack trace should be captured when obtaining a connection
273   * from the pool.
274   * <p>
275   * This can be used to diagnose a suspected connection pool leak.
276   * </p>
277   * <p>
278   * Obviously this has a performance overhead.
279   * </p>
280   */
281  public void setCaptureStackTrace(boolean captureStackTrace) {
282    this.captureStackTrace = captureStackTrace;
283  }
284
285  /**
286   * Return the max size for reporting stack traces on busy connections.
287   */
288  public int getMaxStackTraceSize() {
289    return maxStackTraceSize;
290  }
291
292  /**
293   * Set the max size for reporting stack traces on busy connections.
294   */
295  public void setMaxStackTraceSize(int maxStackTraceSize) {
296    this.maxStackTraceSize = maxStackTraceSize;
297  }
298
299  /**
300   * Return the time in minutes after which a connection could be considered to
301   * have leaked.
302   */
303  public int getLeakTimeMinutes() {
304    return leakTimeMinutes;
305  }
306
307  /**
308   * Set the time in minutes after which a connection could be considered to
309   * have leaked.
310   */
311  public void setLeakTimeMinutes(int leakTimeMinutes) {
312    this.leakTimeMinutes = leakTimeMinutes;
313  }
314
315  /**
316   * Return the size of the PreparedStatement cache (per connection).
317   */
318  public int getPstmtCacheSize() {
319    return pstmtCacheSize;
320  }
321
322  /**
323   * Set the size of the PreparedStatement cache (per connection).
324   */
325  public void setPstmtCacheSize(int pstmtCacheSize) {
326    this.pstmtCacheSize = pstmtCacheSize;
327  }
328
329  /**
330   * Return the size of the CallableStatement cache (per connection).
331   */
332  public int getCstmtCacheSize() {
333    return cstmtCacheSize;
334  }
335
336  /**
337   * Set the size of the CallableStatement cache (per connection).
338   */
339  public void setCstmtCacheSize(int cstmtCacheSize) {
340    this.cstmtCacheSize = cstmtCacheSize;
341  }
342
343  /**
344   * Return the time in millis to wait for a connection before timing out once
345   * the pool has reached its maximum size.
346   */
347  public int getWaitTimeoutMillis() {
348    return waitTimeoutMillis;
349  }
350
351  /**
352   * Set the time in millis to wait for a connection before timing out once the
353   * pool has reached its maximum size.
354   */
355  public void setWaitTimeoutMillis(int waitTimeoutMillis) {
356    this.waitTimeoutMillis = waitTimeoutMillis;
357  }
358
359  /**
360   * Return the time in seconds a connection can be idle after which it can be
361   * trimmed from the pool.
362   * <p>
363   * This is so that the pool after a busy period can trend over time back
364   * towards the minimum connections.
365   * </p>
366   */
367  public int getMaxInactiveTimeSecs() {
368    return maxInactiveTimeSecs;
369  }
370
371  /**
372   * Return the maximum age a connection is allowed to be before it is closed.
373   * <p>
374   * This can be used to close really old connections.
375   * </p>
376   */
377  public int getMaxAgeMinutes() {
378    return maxAgeMinutes;
379  }
380  
381  /**
382   * Set the maximum age a connection can be in minutes.
383   */
384  public void setMaxAgeMinutes(int maxAgeMinutes) {
385    this.maxAgeMinutes = maxAgeMinutes;
386  }
387
388  /**
389   * Set the time in seconds a connection can be idle after which it can be
390   * trimmed from the pool.
391   * <p>
392   * This is so that the pool after a busy period can trend over time back
393   * towards the minimum connections.
394   * </p>
395   */
396  public void setMaxInactiveTimeSecs(int maxInactiveTimeSecs) {
397    this.maxInactiveTimeSecs = maxInactiveTimeSecs;
398  }
399
400  
401  /**
402   * Return the minimum time gap between pool trim checks.
403   * <p>
404   * This defaults to 59 seconds meaning that the pool trim check will run every
405   * minute assuming the heart beat check runs every 30 seconds.
406   * </p>
407   */
408  public int getTrimPoolFreqSecs() {
409    return trimPoolFreqSecs;
410  }
411
412  /**
413   * Set the minimum trim gap between pool trim checks.
414   */
415  public void setTrimPoolFreqSecs(int trimPoolFreqSecs) {
416    this.trimPoolFreqSecs = trimPoolFreqSecs;
417  }
418
419  /**
420   * Return the pool listener.
421   */
422  public String getPoolListener() {
423    return poolListener;
424  }
425
426  /**
427   * Set a pool listener.
428   */
429  public void setPoolListener(String poolListener) {
430    this.poolListener = poolListener;
431  }
432
433  /**
434   * Return true if the DataSource should be left offline.
435   * <p>
436   * This is to support DDL generation etc without having a real database.
437   * </p>
438   */
439  public boolean isOffline() {
440    return offline;
441  }
442
443  /**
444   * Set to true if the DataSource should be left offline.
445   */
446  public void setOffline(boolean offline) {
447    this.offline = offline;
448  }
449  
450  /**
451   * Return a map of custom properties for the jdbc driver connection.
452   */
453  public Map<String, String> getCustomProperties() {
454    return customProperties;
455  }
456
457  /**
458   * Set custom properties for the jdbc driver connection.
459   */
460  public void setCustomProperties(Map<String, String> customProperties) {
461    this.customProperties = customProperties;
462  }
463
464  /**
465   * Load the settings from the properties supplied.
466   * <p>
467   * You can use this when you have your own properties to use for configuration.
468   * </p>
469   *
470   * @param properties the properties to configure the dataSource
471   * @param serverName the name of the specific dataSource (optional)
472   */
473  public void loadSettings(Properties properties, String serverName) {
474    ConfigPropertiesHelper dbProps = new ConfigPropertiesHelper("datasource", serverName, properties);
475    loadSettings(dbProps);
476  }
477
478  /**
479   * Load the settings from the PropertiesWrapper.
480   */
481  private void loadSettings(ConfigPropertiesHelper properties) {
482
483    username = properties.get("username", username);
484    password = properties.get("password", password);
485    driver = properties.get("driver", properties.get("databaseDriver", driver));
486    url = properties.get("url", properties.get("databaseUrl", url));
487
488    autoCommit = properties.getBoolean("autoCommit", autoCommit);
489    captureStackTrace = properties.getBoolean("captureStackTrace", captureStackTrace);
490    maxStackTraceSize = properties.getInt("maxStackTraceSize", maxStackTraceSize);
491    leakTimeMinutes = properties.getInt("leakTimeMinutes", leakTimeMinutes);
492    maxInactiveTimeSecs = properties.getInt("maxInactiveTimeSecs", maxInactiveTimeSecs);
493    trimPoolFreqSecs = properties.getInt("trimPoolFreqSecs", trimPoolFreqSecs);
494    maxAgeMinutes = properties.getInt("maxAgeMinutes", maxAgeMinutes);
495
496    minConnections = properties.getInt("minConnections", minConnections);
497    maxConnections = properties.getInt("maxConnections", maxConnections);
498    pstmtCacheSize = properties.getInt("pstmtCacheSize", pstmtCacheSize);
499    cstmtCacheSize = properties.getInt("cstmtCacheSize", cstmtCacheSize);
500
501    waitTimeoutMillis = properties.getInt("waitTimeout", waitTimeoutMillis);
502
503    heartbeatSql = properties.get("heartbeatSql", heartbeatSql);
504    heartbeatTimeoutSeconds =  properties.getInt("heartbeatTimeoutSeconds", heartbeatTimeoutSeconds);
505    poolListener = properties.get("poolListener", poolListener);
506    offline = properties.getBoolean("offline", offline);
507
508    String isoLevel = properties.get("isolationLevel", getTransactionIsolationLevel(isolationLevel));
509    this.isolationLevel = getTransactionIsolationLevel(isoLevel);
510
511    String customProperties = properties.get("customProperties", null);
512    if (customProperties != null && customProperties.length() > 0) {
513      this.customProperties = parseCustom(customProperties);
514    }
515  }
516
517  Map<String, String> parseCustom(String customProperties) {
518
519    Map<String,String> propertyMap = new LinkedHashMap<String, String>();
520    String[] pairs = customProperties.split(";");
521    for (String pair : pairs) {
522      String[] split = pair.split("=");
523      if (split.length == 2) {
524        propertyMap.put(split[0], split[1]);
525      }
526    }
527    return propertyMap;
528  }
529
530  /**
531   * Return the isolation level description from the associated Connection int value.
532   */
533  private String getTransactionIsolationLevel(int level) {
534    switch (level) {
535      case Connection.TRANSACTION_NONE : return "NONE";
536      case Connection.TRANSACTION_READ_COMMITTED : return "READ_COMMITTED";
537      case Connection.TRANSACTION_READ_UNCOMMITTED : return "READ_UNCOMMITTED";
538      case Connection.TRANSACTION_REPEATABLE_READ : return "REPEATABLE_READ";
539      case Connection.TRANSACTION_SERIALIZABLE : return "SERIALIZABLE";
540      default: throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
541    }
542  }
543
544  /**
545   * Return the isolation level for a given string description.
546   */
547  private int getTransactionIsolationLevel(String level) {
548    level = level.toUpperCase();
549    if (level.startsWith("TRANSACTION")) {
550      level = level.substring("TRANSACTION".length());
551    }
552    level = level.replace("_", "");
553    if ("NONE".equalsIgnoreCase(level)) {
554      return Connection.TRANSACTION_NONE;
555    }
556    if ("READCOMMITTED".equalsIgnoreCase(level)) {
557      return Connection.TRANSACTION_READ_COMMITTED;
558    }
559    if ("READUNCOMMITTED".equalsIgnoreCase(level)) {
560      return Connection.TRANSACTION_READ_UNCOMMITTED;
561    }
562    if ("REPEATABLEREAD".equalsIgnoreCase(level)) {
563      return Connection.TRANSACTION_REPEATABLE_READ;
564    }
565    if ("SERIALIZABLE".equalsIgnoreCase(level)) {
566      return Connection.TRANSACTION_SERIALIZABLE;
567    }
568
569    throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
570  }
571}