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