/** * Copyright 2015 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamsets.pipeline.lib.jdbc; import com.streamsets.pipeline.api.ValueChooserModel; import com.streamsets.pipeline.lib.el.VaultEL; import com.streamsets.pipeline.api.ConfigDef; import com.streamsets.pipeline.api.Stage; import com.streamsets.pipeline.lib.el.TimeEL; import com.streamsets.pipeline.stage.destination.jdbc.Groups; import java.util.HashMap; import java.util.List; import java.util.Map; public class HikariPoolConfigBean { private static final int TEN_MINUTES = 600; private static final int THIRTY_MINUTES = 1800; private static final int THIRTY_SECONDS = 30; private static final String DEFAULT_CONNECTION_TIMEOUT_EL = "${30 * SECONDS}"; private static final String DEFAULT_IDLE_TIMEOUT_EL = "${10 * MINUTES}"; private static final String DEFAULT_MAX_LIFETIME_EL = "${30 * MINUTES}"; private static final int MAX_POOL_SIZE_MIN = 1; private static final int MIN_IDLE_MIN = 0; private static final int CONNECTION_TIMEOUT_MIN = 1; private static final int IDLE_TIMEOUT_MIN = 0; private static final int MAX_LIFETIME_MIN = 0; public static final int MILLISECONDS = 1000; public static final int DEFAULT_CONNECTION_TIMEOUT = THIRTY_SECONDS; public static final int DEFAULT_IDLE_TIMEOUT = TEN_MINUTES; public static final int DEFAULT_MAX_LIFETIME = THIRTY_MINUTES; public static final int DEFAULT_MAX_POOL_SIZE = 1; public static final int DEFAULT_MIN_IDLE = 1; public static final boolean DEFAULT_READ_ONLY = true; public static final String HIKARI_BEAN_NAME = "hikariConfigBean."; public static final String MAX_POOL_SIZE_NAME = "maximumPoolSize"; public static final String MIN_IDLE_NAME = "minIdle"; public static final String CONNECTION_TIMEOUT_NAME = "connectionTimeout"; public static final String IDLE_TIMEOUT_NAME = "idleTimeout"; public static final String MAX_LIFETIME_NAME = "maxLifetime"; public static final String READ_ONLY_NAME = "readOnly"; @ConfigDef( required = true, type = ConfigDef.Type.STRING, label = "JDBC Connection String", displayPosition = 10, group = "JDBC" ) public String connectionString = ""; @ConfigDef( required = true, type = ConfigDef.Type.BOOLEAN, defaultValue = "true", label = "Use Credentials", displayPosition = 11, group = "JDBC" ) public boolean useCredentials; @ConfigDef( required = true, type = ConfigDef.Type.STRING, dependsOn = "useCredentials", triggeredByValue = "true", label = "Username", displayPosition = 110, elDefs = VaultEL.class, group = "CREDENTIALS" ) public String username; @ConfigDef( required = true, type = ConfigDef.Type.STRING, dependsOn = "useCredentials", triggeredByValue = "true", label = "Password", displayPosition = 120, elDefs = VaultEL.class, group = "CREDENTIALS" ) public String password; @ConfigDef( required = false, type = ConfigDef.Type.MAP, defaultValue = "", label = "Additional JDBC Configuration Properties", description = "Additional properties to pass to the underlying JDBC driver.", displayPosition = 999, elDefs = VaultEL.class, group = "JDBC" ) public Map<String, String> driverProperties = new HashMap<>(); @ConfigDef( required = false, type = ConfigDef.Type.STRING, label = "JDBC Driver Class Name", description = "Class name for pre-JDBC 4 compliant drivers.", displayPosition = 10, group = "LEGACY" ) public String driverClassName = ""; @ConfigDef( required = false, type = ConfigDef.Type.TEXT, mode = ConfigDef.Mode.SQL, label = "Connection Health Test Query", description = "Not recommended for JDBC 4 compliant drivers. Runs when a new database connection is established.", displayPosition = 20, group = "LEGACY" ) public String connectionTestQuery = ""; @ConfigDef( required = true, type = ConfigDef.Type.NUMBER, label = "Maximum Pool Size", description = "Maximum number of connections to create to the data source", min = 1, defaultValue = "1", displayPosition = 10, group = "ADVANCED" ) public int maximumPoolSize = DEFAULT_MAX_POOL_SIZE; @ConfigDef( required = true, type = ConfigDef.Type.NUMBER, label = "Minimum Idle Connections", description = "Minimum number of connections to maintain. It is recommended to set this to the same value" + "as Maximum Pool Size which effectively creates a fixed connection pool.", min = 0, defaultValue = "1", displayPosition = 20, group = "ADVANCED" ) public int minIdle = DEFAULT_MIN_IDLE; @ConfigDef( required = true, type = ConfigDef.Type.NUMBER, label = "Connection Timeout", description = "Maximum time to wait for a connection to become available. Exceeding will cause a pipeline error.", min = 1, defaultValue = DEFAULT_CONNECTION_TIMEOUT_EL, elDefs = {TimeEL.class}, displayPosition = 30, group = "ADVANCED" ) public int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; @ConfigDef( required = true, type = ConfigDef.Type.NUMBER, label = "Idle Timeout", description = "Maximum amount of time that a connection is allowed to sit idle in the pool. 0 means don't " + "remove idle connections.", min = 0, defaultValue = DEFAULT_IDLE_TIMEOUT_EL, elDefs = {TimeEL.class}, displayPosition = 40, group = "ADVANCED" ) public int idleTimeout = DEFAULT_IDLE_TIMEOUT; @ConfigDef( required = true, type = ConfigDef.Type.NUMBER, label = "Max Connection Lifetime", description = "Maximum lifetime of a connection in the pool. When reached it will be retired from the pool. 0 " + "means no maximum lifetime.", min = 0, defaultValue = DEFAULT_MAX_LIFETIME_EL, elDefs = {TimeEL.class}, displayPosition = 50, group = "ADVANCED" ) public int maxLifetime = DEFAULT_MAX_LIFETIME; @ConfigDef( required = true, type = ConfigDef.Type.BOOLEAN, label = "Auto Commit", description = "Whether the connection should have property auto-commit set to true or not.", defaultValue = "false", displayPosition = 55, group = "ADVANCED" ) public boolean autoCommit = false; @ConfigDef( required = true, type = ConfigDef.Type.BOOLEAN, label = "Enforce Read-only Connection", description = "Should be set to true whenever possible to avoid unintended writes. Set to false with extreme " + "caution.", defaultValue = "true", displayPosition = 60, group = "ADVANCED" ) public boolean readOnly = true; @ConfigDef( required = true, type = ConfigDef.Type.MODEL, label = "Transaction isolation", description = "Transaction isolation that should be used for all database connections.", defaultValue = "DEFAULT", displayPosition = 70, group = "ADVANCED" ) @ValueChooserModel(TransactionIsolationLevelChooserValues.class) public TransactionIsolationLevel transactionIsolation = TransactionIsolationLevel.DEFAULT; private static final String HIKARI_CONFIG_PREFIX = "hikariConfigBean."; private static final String DRIVER_CLASSNAME = HIKARI_CONFIG_PREFIX + "driverClassName"; public List<Stage.ConfigIssue> validateConfigs(Stage.Context context, List<Stage.ConfigIssue> issues) { // Validation for NUMBER fields is currently disabled due to allowing ELs so we do our own here. if (maximumPoolSize < MAX_POOL_SIZE_MIN) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), MAX_POOL_SIZE_NAME, JdbcErrors.JDBC_10, maximumPoolSize, MAX_POOL_SIZE_NAME ) ); } if (minIdle < MIN_IDLE_MIN) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), MIN_IDLE_NAME, JdbcErrors.JDBC_10, minIdle, MIN_IDLE_MIN ) ); } if (minIdle > maximumPoolSize) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), MIN_IDLE_NAME, JdbcErrors.JDBC_11, minIdle, maximumPoolSize ) ); } if (connectionTimeout < CONNECTION_TIMEOUT_MIN) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), CONNECTION_TIMEOUT_NAME, JdbcErrors.JDBC_10, connectionTimeout, CONNECTION_TIMEOUT_MIN ) ); } if (idleTimeout < IDLE_TIMEOUT_MIN) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), IDLE_TIMEOUT_NAME, JdbcErrors.JDBC_10, idleTimeout, IDLE_TIMEOUT_MIN ) ); } if (maxLifetime < MAX_LIFETIME_MIN) { issues.add( context.createConfigIssue( Groups.ADVANCED.name(), MAX_LIFETIME_NAME, JdbcErrors.JDBC_10, maxLifetime, MAX_LIFETIME_MIN ) ); } if (!driverClassName.isEmpty()) { try { Class.forName(driverClassName); } catch (ClassNotFoundException e) { issues.add(context.createConfigIssue(com.streamsets.pipeline.stage.origin.jdbc.Groups.LEGACY.name(), DRIVER_CLASSNAME, JdbcErrors.JDBC_28, e.toString())); } } return issues; } }