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}