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