001package io.ebean.datasource;
002
003import java.sql.Connection;
004import java.util.ArrayList;
005import java.util.LinkedHashMap;
006import java.util.List;
007import java.util.Map;
008import java.util.Properties;
009
010/**
011 * Configuration information for a DataSource.
012 */
013public class DataSourceConfig {
014
015  private static final String POSTGRES = "postgres";
016
017  private InitDatabase initDatabase;
018
019  private String url;
020
021  private String username;
022
023  private String password;
024
025  /**
026   * The name of the database platform (for use with ownerUsername and InitDatabase).
027   */
028  private String platform;
029
030  /**
031   * The optional database owner username (for running InitDatabase).
032   */
033  private String ownerUsername;
034
035  /**
036   * The optional database owner password (for running InitDatabase).
037   */
038  private String ownerPassword;
039
040  private String driver;
041
042  private int minConnections = 2;
043
044  private int maxConnections = 200;
045
046  private int isolationLevel = Connection.TRANSACTION_READ_COMMITTED;
047
048  private boolean autoCommit;
049
050  private boolean readOnly;
051
052  private String heartbeatSql;
053
054  private int heartbeatFreqSecs = 30;
055
056  private int heartbeatTimeoutSeconds = 3;
057
058  private boolean captureStackTrace;
059
060  private int maxStackTraceSize = 5;
061
062  private int leakTimeMinutes = 30;
063
064  private int maxInactiveTimeSecs = 300;
065
066  private int maxAgeMinutes = 0;
067
068  private int trimPoolFreqSecs = 59;
069
070  private int pstmtCacheSize = 20;
071
072  private int cstmtCacheSize = 20;
073
074  private int waitTimeoutMillis = 1000;
075
076  private String poolListener;
077
078  private boolean offline;
079
080  private boolean failOnStart = true;
081
082  private Map<String, String> customProperties;
083
084  private List<String> initSql;
085
086
087  private DataSourceAlert alert;
088
089  private DataSourcePoolListener listener;
090
091  /**
092   * Default the values for driver, url, username and password from another config if
093   * they have not been set.
094   */
095  public DataSourceConfig setDefaults(DataSourceConfig other) {
096    if (driver == null) {
097      driver = other.driver;
098    }
099    if (url == null) {
100      url = other.url;
101    }
102    if (username == null) {
103      username = other.username;
104    }
105    if (password == null) {
106      password = other.password;
107    }
108    return this;
109  }
110
111  /**
112   * Return true if there are no values set for any of url, driver, username and password.
113   */
114  public boolean isEmpty() {
115    return url == null
116      && driver == null
117      && username == null
118      && password == null;
119  }
120
121  /**
122   * Return the connection URL.
123   */
124  public String getUrl() {
125    return url;
126  }
127
128  /**
129   * Set the connection URL.
130   */
131  public DataSourceConfig setUrl(String url) {
132    this.url = url;
133    return this;
134  }
135
136  /**
137   * Return the database username.
138   */
139  public String getUsername() {
140    return username;
141  }
142
143  /**
144   * Set the database username.
145   */
146  public DataSourceConfig setUsername(String username) {
147    this.username = username;
148    return this;
149  }
150
151  /**
152   * Return the database password.
153   */
154  public String getPassword() {
155    return password;
156  }
157
158  /**
159   * Set the database password.
160   */
161  public DataSourceConfig setPassword(String password) {
162    this.password = password;
163    return this;
164  }
165
166  /**
167   * Return the database driver.
168   */
169  public String getDriver() {
170    return driver;
171  }
172
173  /**
174   * Set the database driver.
175   */
176  public DataSourceConfig setDriver(String driver) {
177    this.driver = driver;
178    return this;
179  }
180
181  /**
182   * Return the transaction isolation level.
183   */
184  public int getIsolationLevel() {
185    return isolationLevel;
186  }
187
188  /**
189   * Set the transaction isolation level.
190   */
191  public DataSourceConfig setIsolationLevel(int isolationLevel) {
192    this.isolationLevel = isolationLevel;
193    return this;
194  }
195
196  /**
197   * Return autoCommit setting.
198   */
199  public boolean isAutoCommit() {
200    return autoCommit;
201  }
202
203  /**
204   * Set to true to turn on autoCommit.
205   */
206  public DataSourceConfig setAutoCommit(boolean autoCommit) {
207    this.autoCommit = autoCommit;
208    return this;
209  }
210
211  /**
212   * Return the read only setting.
213   */
214  public boolean isReadOnly() {
215    return readOnly;
216  }
217
218  /**
219   * Set to true to for read only.
220   */
221  public DataSourceConfig setReadOnly(boolean readOnly) {
222    this.readOnly = readOnly;
223    return this;
224  }
225
226  /**
227   * Return the minimum number of connections the pool should maintain.
228   */
229  public int getMinConnections() {
230    return minConnections;
231  }
232
233  /**
234   * Set the minimum number of connections the pool should maintain.
235   */
236  public DataSourceConfig setMinConnections(int minConnections) {
237    this.minConnections = minConnections;
238    return this;
239  }
240
241  /**
242   * Return the maximum number of connections the pool can reach.
243   */
244  public int getMaxConnections() {
245    return maxConnections;
246  }
247
248  /**
249   * Set the maximum number of connections the pool can reach.
250   */
251  public DataSourceConfig setMaxConnections(int maxConnections) {
252    this.maxConnections = maxConnections;
253    return this;
254  }
255
256  /**
257   * Return the alert implementation to use.
258   */
259  public DataSourceAlert getAlert() {
260    return alert;
261  }
262
263  /**
264   * Set the alert implementation to use.
265   */
266  public DataSourceConfig setAlert(DataSourceAlert alert) {
267    this.alert = alert;
268    return this;
269  }
270
271  /**
272   * Return the listener to use.
273   */
274  public DataSourcePoolListener getListener() {
275    return listener;
276  }
277
278  /**
279   * Set the listener to use.
280   */
281  public DataSourceConfig setListener(DataSourcePoolListener listener) {
282    this.listener = listener;
283    return this;
284  }
285
286  /**
287   * Return a SQL statement used to test the database is accessible.
288   * <p>
289   * Note that if this is not set then it can get defaulted from the
290   * DatabasePlatform.
291   * </p>
292   */
293  public String getHeartbeatSql() {
294    return heartbeatSql;
295  }
296
297  /**
298   * Set a SQL statement used to test the database is accessible.
299   * <p>
300   * Note that if this is not set then it can get defaulted from the
301   * DatabasePlatform.
302   * </p>
303   */
304  public DataSourceConfig setHeartbeatSql(String heartbeatSql) {
305    this.heartbeatSql = heartbeatSql;
306    return this;
307  }
308
309  /**
310   * Return the heartbeat frequency in seconds.
311   * <p>
312   * This is the expected frequency in which the DataSource should be checked to
313   * make sure it is healthy and trim idle connections.
314   * </p>
315   */
316  public int getHeartbeatFreqSecs() {
317    return heartbeatFreqSecs;
318  }
319
320  /**
321   * Set the expected heartbeat frequency in seconds.
322   */
323  public DataSourceConfig setHeartbeatFreqSecs(int heartbeatFreqSecs) {
324    this.heartbeatFreqSecs = heartbeatFreqSecs;
325    return this;
326  }
327
328  /**
329   * Return the heart beat timeout in seconds.
330   */
331  public int getHeartbeatTimeoutSeconds() {
332    return heartbeatTimeoutSeconds;
333  }
334
335  /**
336   * Set the heart beat timeout in seconds.
337   */
338  public DataSourceConfig setHeartbeatTimeoutSeconds(int heartbeatTimeoutSeconds) {
339    this.heartbeatTimeoutSeconds = heartbeatTimeoutSeconds;
340    return this;
341  }
342
343  /**
344   * Return true if a stack trace should be captured when obtaining a connection
345   * from the pool.
346   * <p>
347   * This can be used to diagnose a suspected connection pool leak.
348   * </p>
349   * <p>
350   * Obviously this has a performance overhead.
351   * </p>
352   */
353  public boolean isCaptureStackTrace() {
354    return captureStackTrace;
355  }
356
357  /**
358   * Set to true if a stack trace should be captured when obtaining a connection
359   * from the pool.
360   * <p>
361   * This can be used to diagnose a suspected connection pool leak.
362   * </p>
363   * <p>
364   * Obviously this has a performance overhead.
365   * </p>
366   */
367  public DataSourceConfig setCaptureStackTrace(boolean captureStackTrace) {
368    this.captureStackTrace = captureStackTrace;
369    return this;
370  }
371
372  /**
373   * Return the max size for reporting stack traces on busy connections.
374   */
375  public int getMaxStackTraceSize() {
376    return maxStackTraceSize;
377  }
378
379  /**
380   * Set the max size for reporting stack traces on busy connections.
381   */
382  public DataSourceConfig setMaxStackTraceSize(int maxStackTraceSize) {
383    this.maxStackTraceSize = maxStackTraceSize;
384    return this;
385  }
386
387  /**
388   * Return the time in minutes after which a connection could be considered to
389   * have leaked.
390   */
391  public int getLeakTimeMinutes() {
392    return leakTimeMinutes;
393  }
394
395  /**
396   * Set the time in minutes after which a connection could be considered to
397   * have leaked.
398   */
399  public DataSourceConfig setLeakTimeMinutes(int leakTimeMinutes) {
400    this.leakTimeMinutes = leakTimeMinutes;
401    return this;
402  }
403
404  /**
405   * Return the size of the PreparedStatement cache (per connection).
406   */
407  public int getPstmtCacheSize() {
408    return pstmtCacheSize;
409  }
410
411  /**
412   * Set the size of the PreparedStatement cache (per connection).
413   */
414  public DataSourceConfig setPstmtCacheSize(int pstmtCacheSize) {
415    this.pstmtCacheSize = pstmtCacheSize;
416    return this;
417  }
418
419  /**
420   * Return the size of the CallableStatement cache (per connection).
421   */
422  public int getCstmtCacheSize() {
423    return cstmtCacheSize;
424  }
425
426  /**
427   * Set the size of the CallableStatement cache (per connection).
428   */
429  public DataSourceConfig setCstmtCacheSize(int cstmtCacheSize) {
430    this.cstmtCacheSize = cstmtCacheSize;
431    return this;
432  }
433
434  /**
435   * Return the time in millis to wait for a connection before timing out once
436   * the pool has reached its maximum size.
437   */
438  public int getWaitTimeoutMillis() {
439    return waitTimeoutMillis;
440  }
441
442  /**
443   * Set the time in millis to wait for a connection before timing out once the
444   * pool has reached its maximum size.
445   */
446  public DataSourceConfig setWaitTimeoutMillis(int waitTimeoutMillis) {
447    this.waitTimeoutMillis = waitTimeoutMillis;
448    return this;
449  }
450
451  /**
452   * Return the time in seconds a connection can be idle after which it can be
453   * trimmed from the pool.
454   * <p>
455   * This is so that the pool after a busy period can trend over time back
456   * towards the minimum connections.
457   * </p>
458   */
459  public int getMaxInactiveTimeSecs() {
460    return maxInactiveTimeSecs;
461  }
462
463  /**
464   * Return the maximum age a connection is allowed to be before it is closed.
465   * <p>
466   * This can be used to close really old connections.
467   * </p>
468   */
469  public int getMaxAgeMinutes() {
470    return maxAgeMinutes;
471  }
472
473  /**
474   * Set the maximum age a connection can be in minutes.
475   */
476  public DataSourceConfig setMaxAgeMinutes(int maxAgeMinutes) {
477    this.maxAgeMinutes = maxAgeMinutes;
478    return this;
479  }
480
481  /**
482   * Set the time in seconds a connection can be idle after which it can be
483   * trimmed from the pool.
484   * <p>
485   * This is so that the pool after a busy period can trend over time back
486   * towards the minimum connections.
487   * </p>
488   */
489  public DataSourceConfig setMaxInactiveTimeSecs(int maxInactiveTimeSecs) {
490    this.maxInactiveTimeSecs = maxInactiveTimeSecs;
491    return this;
492  }
493
494
495  /**
496   * Return the minimum time gap between pool trim checks.
497   * <p>
498   * This defaults to 59 seconds meaning that the pool trim check will run every
499   * minute assuming the heart beat check runs every 30 seconds.
500   * </p>
501   */
502  public int getTrimPoolFreqSecs() {
503    return trimPoolFreqSecs;
504  }
505
506  /**
507   * Set the minimum trim gap between pool trim checks.
508   */
509  public DataSourceConfig setTrimPoolFreqSecs(int trimPoolFreqSecs) {
510    this.trimPoolFreqSecs = trimPoolFreqSecs;
511    return this;
512  }
513
514  /**
515   * Return the pool listener.
516   */
517  public String getPoolListener() {
518    return poolListener;
519  }
520
521  /**
522   * Set a pool listener.
523   */
524  public DataSourceConfig setPoolListener(String poolListener) {
525    this.poolListener = poolListener;
526    return this;
527  }
528
529  /**
530   * Return true if the DataSource should be left offline.
531   * <p>
532   * This is to support DDL generation etc without having a real database.
533   * </p>
534   */
535  public boolean isOffline() {
536    return offline;
537  }
538
539  /**
540   * Return true (default) if the DataSource should be fail on start.
541   * <p>
542   * This enables to initialize the Ebean-Server if the db-server is not yet up.
543   * ({@link DataSourceAlert#dataSourceUp(javax.sql.DataSource)} is fired when DS gets up later.)
544   * </p>
545   */
546  public boolean isFailOnStart() {
547    return failOnStart;
548  }
549
550  /**
551   * Set to false, if DataSource should not fail on start. (e.g. DataSource is not available)
552   */
553  public DataSourceConfig setFailOnStart(boolean failOnStart) {
554    this.failOnStart = failOnStart;
555    return this;
556  }
557
558  /**
559   * Set to true if the DataSource should be left offline.
560   */
561  public DataSourceConfig setOffline(boolean offline) {
562    this.offline = offline;
563    return this;
564  }
565
566  /**
567   * Return a map of custom properties for the jdbc driver connection.
568   */
569  public Map<String, String> getCustomProperties() {
570    return customProperties;
571  }
572
573  /**
574   * Return a list of init queries, that are executed after a connection is opened.
575   */
576  public List<String> getInitSql() {
577    return initSql;
578  }
579
580  /**
581   * Set custom init queries for each query.
582   */
583  public DataSourceConfig setInitSql(List<String> initSql) {
584    this.initSql = initSql;
585    return this;
586  }
587
588  /**
589   * Set custom properties for the jdbc driver connection.
590   */
591  public DataSourceConfig setCustomProperties(Map<String, String> customProperties) {
592    this.customProperties = customProperties;
593    return this;
594  }
595
596  /**
597   * Return the database owner username.
598   */
599  public String getOwnerUsername() {
600    return ownerUsername;
601  }
602
603  /**
604   * Set the database owner username (used to create connection for use with InitDatabase).
605   */
606  public DataSourceConfig setOwnerUsername(String ownerUsername) {
607    this.ownerUsername = ownerUsername;
608    return this;
609  }
610
611  /**
612   * Return the database owner password.
613   */
614  public String getOwnerPassword() {
615    return ownerPassword;
616  }
617
618  /**
619   * Set the database owner password (used to create connection for use with InitDatabase).
620   */
621  public DataSourceConfig setOwnerPassword(String ownerPassword) {
622    this.ownerPassword = ownerPassword;
623    return this;
624  }
625
626  /**
627   * Return the database platform.
628   */
629  public String getPlatform() {
630    return platform;
631  }
632
633  /**
634   * Set the database platform (for use with ownerUsername and InitDatabase.
635   */
636  public DataSourceConfig setPlatform(String platform) {
637    this.platform = platform;
638    if (initDatabase != null) {
639      setInitDatabaseForPlatform(platform);
640    }
641    return this;
642  }
643
644  /**
645   * Return the InitDatabase to use with ownerUsername.
646   */
647  public InitDatabase getInitDatabase() {
648    return initDatabase;
649  }
650
651  /**
652   * Set the InitDatabase to use with ownerUsername.
653   */
654  public DataSourceConfig setInitDatabase(InitDatabase initDatabase) {
655    this.initDatabase = initDatabase;
656    return this;
657  }
658
659  /**
660   * Set InitDatabase based on the database platform.
661   */
662  public DataSourceConfig setInitDatabaseForPlatform(String platform) {
663    if (platform != null) {
664      switch (platform.toLowerCase()) {
665        case POSTGRES:
666          initDatabase = new PostgresInitDatabase();
667          break;
668      }
669    }
670    return this;
671  }
672
673  /**
674   * Return true if InitDatabase should be used (when the pool initialises and a connection can't be obtained).
675   *
676   * @return True to obtain a connection using ownerUsername and run InitDatabase.
677   */
678  public boolean useInitDatabase() {
679    if (ownerUsername != null && ownerPassword != null) {
680      if (initDatabase == null) {
681        // default to postgres
682        initDatabase = new PostgresInitDatabase();
683      }
684      return true;
685    }
686    return false;
687  }
688
689  /**
690   * Load the settings from the properties supplied.
691   * <p>
692   * You can use this when you have your own properties to use for configuration.
693   * </p>
694   *
695   * @param properties the properties to configure the dataSource
696   * @param serverName the name of the specific dataSource (optional)
697   */
698  public DataSourceConfig loadSettings(Properties properties, String serverName) {
699    ConfigPropertiesHelper dbProps = new ConfigPropertiesHelper("datasource", serverName, properties);
700    loadSettings(dbProps);
701    return this;
702  }
703
704  /**
705   * Load the settings from the PropertiesWrapper.
706   */
707  private void loadSettings(ConfigPropertiesHelper properties) {
708
709    username = properties.get("username", username);
710    password = properties.get("password", password);
711    platform = properties.get("platform", platform);
712    ownerUsername = properties.get("ownerUsername", ownerUsername);
713    ownerPassword = properties.get("ownerPassword", ownerPassword);
714    if (initDatabase == null && platform != null) {
715      setInitDatabaseForPlatform(platform);
716    }
717
718    driver = properties.get("driver", properties.get("databaseDriver", driver));
719    url = properties.get("url", properties.get("databaseUrl", url));
720    autoCommit = properties.getBoolean("autoCommit", autoCommit);
721    readOnly = properties.getBoolean("readOnly", readOnly);
722    captureStackTrace = properties.getBoolean("captureStackTrace", captureStackTrace);
723    maxStackTraceSize = properties.getInt("maxStackTraceSize", maxStackTraceSize);
724    leakTimeMinutes = properties.getInt("leakTimeMinutes", leakTimeMinutes);
725    maxInactiveTimeSecs = properties.getInt("maxInactiveTimeSecs", maxInactiveTimeSecs);
726    trimPoolFreqSecs = properties.getInt("trimPoolFreqSecs", trimPoolFreqSecs);
727    maxAgeMinutes = properties.getInt("maxAgeMinutes", maxAgeMinutes);
728
729    minConnections = properties.getInt("minConnections", minConnections);
730    maxConnections = properties.getInt("maxConnections", maxConnections);
731    pstmtCacheSize = properties.getInt("pstmtCacheSize", pstmtCacheSize);
732    cstmtCacheSize = properties.getInt("cstmtCacheSize", cstmtCacheSize);
733
734    waitTimeoutMillis = properties.getInt("waitTimeout", waitTimeoutMillis);
735
736    heartbeatSql = properties.get("heartbeatSql", heartbeatSql);
737    heartbeatTimeoutSeconds = properties.getInt("heartbeatTimeoutSeconds", heartbeatTimeoutSeconds);
738    poolListener = properties.get("poolListener", poolListener);
739    offline = properties.getBoolean("offline", offline);
740
741    String isoLevel = properties.get("isolationLevel", getTransactionIsolationLevel(isolationLevel));
742    this.isolationLevel = getTransactionIsolationLevel(isoLevel);
743
744    this.initSql = parseSql(properties.get("initSql", null));
745    this.failOnStart = properties.getBoolean("failOnStart", failOnStart);
746
747    String customProperties = properties.get("customProperties", null);
748    if (customProperties != null && customProperties.length() > 0) {
749      this.customProperties = parseCustom(customProperties);
750    }
751  }
752
753  private List<String> parseSql(String sql) {
754    List<String> ret = new ArrayList<>();
755    if (sql != null) {
756      String[] queries = sql.split(";");
757      for (String query : queries) {
758        query = query.trim();
759        if (!query.isEmpty()) {
760          ret.add(query);
761        }
762      }
763    }
764    return ret;
765  }
766
767  Map<String, String> parseCustom(String customProperties) {
768
769    Map<String, String> propertyMap = new LinkedHashMap<String, String>();
770    String[] pairs = customProperties.split(";");
771    for (String pair : pairs) {
772      String[] split = pair.split("=");
773      if (split.length == 2) {
774        propertyMap.put(split[0], split[1]);
775      }
776    }
777    return propertyMap;
778  }
779
780  /**
781   * Return the isolation level description from the associated Connection int value.
782   */
783  private String getTransactionIsolationLevel(int level) {
784    switch (level) {
785      case Connection.TRANSACTION_NONE:
786        return "NONE";
787      case Connection.TRANSACTION_READ_COMMITTED:
788        return "READ_COMMITTED";
789      case Connection.TRANSACTION_READ_UNCOMMITTED:
790        return "READ_UNCOMMITTED";
791      case Connection.TRANSACTION_REPEATABLE_READ:
792        return "REPEATABLE_READ";
793      case Connection.TRANSACTION_SERIALIZABLE:
794        return "SERIALIZABLE";
795      default:
796        throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
797    }
798  }
799
800  /**
801   * Return the isolation level for a given string description.
802   */
803  private int getTransactionIsolationLevel(String level) {
804    level = level.toUpperCase();
805    if (level.startsWith("TRANSACTION")) {
806      level = level.substring("TRANSACTION".length());
807    }
808    level = level.replace("_", "");
809    if ("NONE".equalsIgnoreCase(level)) {
810      return Connection.TRANSACTION_NONE;
811    }
812    if ("READCOMMITTED".equalsIgnoreCase(level)) {
813      return Connection.TRANSACTION_READ_COMMITTED;
814    }
815    if ("READUNCOMMITTED".equalsIgnoreCase(level)) {
816      return Connection.TRANSACTION_READ_UNCOMMITTED;
817    }
818    if ("REPEATABLEREAD".equalsIgnoreCase(level)) {
819      return Connection.TRANSACTION_REPEATABLE_READ;
820    }
821    if ("SERIALIZABLE".equalsIgnoreCase(level)) {
822      return Connection.TRANSACTION_SERIALIZABLE;
823    }
824
825    throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
826  }
827}