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