package com.thinkbiganalytics.feedmgr.nifi; /*- * #%L * thinkbig-feed-manager-controller * %% * Copyright (C) 2017 ThinkBig Analytics * %% * Licensed 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. * #L% */ import com.thinkbiganalytics.db.PoolingDataSourceService; import com.thinkbiganalytics.discovery.schema.TableSchema; import com.thinkbiganalytics.jdbc.util.DatabaseType; import com.thinkbiganalytics.kerberos.KerberosTicketConfiguration; import com.thinkbiganalytics.metadata.rest.model.data.JdbcDatasource; import com.thinkbiganalytics.schema.DBSchemaParser; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.web.api.dto.ControllerServiceDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; import javax.sql.DataSource; /** * Allow Kylo to use a NiFi database pool connection to display database metadata with tables and columns. */ @Service public class DBCPConnectionPoolTableInfo { private static final Logger log = LoggerFactory.getLogger(DBCPConnectionPoolTableInfo.class); @Autowired private NifiControllerServiceProperties nifiControllerServiceProperties; @Inject @Qualifier("kerberosHiveConfiguration") private KerberosTicketConfiguration kerberosHiveConfiguration; /** * Returns a list of table names matching a pattern * * @param serviceId a NiFi controller service id * @param serviceName a NiFi controller service name * @param schema A schema pattern to look for * @param tableName A table pattern to look for * @return a list of schema.table names matching the pattern for the database */ public List<String> getTableNamesForControllerService(String serviceId, String serviceName, String schema, String tableName) { ControllerServiceDTO controllerService = getControllerService(serviceId, serviceName); if (controllerService != null) { DescribeTableWithControllerServiceBuilder builder = new DescribeTableWithControllerServiceBuilder(controllerService); DescribeTableWithControllerService serviceProperties = builder.schemaName(schema).tableName(tableName).build(); return getTableNamesForControllerService(serviceProperties); } else { log.error("Cannot getTable Names for Controller Service. Unable to obtain Controller Service for serviceId or Name ({} , {})", serviceId, serviceName); } return null; } /** * Returns a list of table names for the specified data source. * * @param datasource the data source * @param schema the schema name, or {@code null} for all schemas * @param tableName a table pattern to look for * @return a list of schema.table names, or {@code null} if not accessible */ @Nullable public List<String> getTableNamesForDatasource(@Nonnull final JdbcDatasource datasource, @Nullable final String schema, @Nullable final String tableName) { final Optional<ControllerServiceDTO> controllerService = Optional.ofNullable(datasource.getControllerServiceId()) .map(id -> getControllerService(id, null)); if (controllerService.isPresent()) { final DescribeTableWithControllerServiceBuilder builder = new DescribeTableWithControllerServiceBuilder(controllerService.get()); final DescribeTableWithControllerService serviceProperties = builder.schemaName(schema).tableName(tableName).password(datasource.getPassword()).useEnvironmentProperties(false).build(); return getTableNamesForControllerService(serviceProperties); } else { log.error("Cannot get table names for data source: {}", datasource); return null; } } /** * Describe the database table and fields available for a given NiFi controller service * * @param serviceId a NiFi controller service id * @param serviceName a NiFi controller service name * @param schema A schema to look for * @param tableName A table to look for * @return the database table and fields */ public TableSchema describeTableForControllerService(String serviceId, String serviceName, String schema, String tableName) { ControllerServiceDTO controllerService = getControllerService(serviceId, serviceName); if (controllerService != null) { DescribeTableWithControllerServiceBuilder builder = new DescribeTableWithControllerServiceBuilder(controllerService); DescribeTableWithControllerService serviceProperties = builder.schemaName(schema).tableName(tableName).build(); return describeTableForControllerService(serviceProperties); } else { log.error("Cannot describe Table for Controller Service. Unable to obtain Controller Service for serviceId or Name ({} , {})", serviceId, serviceName); } return null; } /** * Describes the specified database table accessed through the specified data source. * * @param datasource the data source * @param schema the schema name, or {@code null} to search all schemas * @param tableName the table name * @return the database table and fields, or {@code null} if not found */ public TableSchema describeTableForDatasource(@Nonnull final JdbcDatasource datasource, @Nullable final String schema, @Nonnull final String tableName) { final Optional<ControllerServiceDTO> controllerService = Optional.ofNullable(datasource.getControllerServiceId()) .map(id -> getControllerService(id, null)); if (controllerService.isPresent()) { final DescribeTableWithControllerServiceBuilder builder = new DescribeTableWithControllerServiceBuilder(controllerService.get()); final DescribeTableWithControllerService serviceProperties = builder.schemaName(schema).tableName(tableName).password(datasource.getPassword()).useEnvironmentProperties(false).build(); return describeTableForControllerService(serviceProperties); } else { log.error("Cannot describe table for data source: {}", datasource); return null; } } /** * Return a list of schema.table_name * * @param serviceProperties properties describing where and what to look for * @return a list of schema.table_name */ private List<String> getTableNamesForControllerService(DescribeTableWithControllerService serviceProperties) { if (serviceProperties != null) { Map<String, String> properties = serviceProperties.useEnvironmentProperties() ? nifiControllerServiceProperties.mergeNifiAndEnvProperties(serviceProperties.getControllerServiceDTO().getProperties(), serviceProperties.getControllerServiceName()) : serviceProperties.getControllerServiceDTO().getProperties(); PoolingDataSourceService.DataSourceProperties dataSourceProperties = getDataSourceProperties(properties, serviceProperties); if (StringUtils.isNotBlank(dataSourceProperties.getPassword()) && dataSourceProperties.getPassword().startsWith("**")) { String propertyKey = nifiControllerServiceProperties.getEnvironmentControllerServicePropertyPrefix(serviceProperties.getControllerServiceName()) + ".password"; String example = propertyKey + "=PASSWORD"; log.error("Unable to connect to Controller Service {}, {}. You need to specifiy a configuration property as {} with the password for user: {}. ", serviceProperties.getControllerServiceName(), serviceProperties.getControllerServiceId(), example, dataSourceProperties.getUser()); } log.info("Search For Tables against Controller Service: {} ({}) with uri of {}. ", serviceProperties.getControllerServiceName(), serviceProperties.getControllerServiceId(), dataSourceProperties.getUrl()); DataSource dataSource = PoolingDataSourceService.getDataSource(dataSourceProperties); DBSchemaParser schemaParser = new DBSchemaParser(dataSource, kerberosHiveConfiguration); return schemaParser.listTables(serviceProperties.getSchemaName(), serviceProperties.getTableName()); } return null; } /** * get the validation query from the db name that is parsed from the */ private String parseValidationQueryFromConnectionString(String connectionString) { String validationQuery = null; try { DatabaseType databaseType = DatabaseType.fromJdbcConnectionString(connectionString); validationQuery = databaseType.getValidationQuery(); } catch (IllegalArgumentException e) { //if we cant find it in the map its ok. } return validationQuery; } private TableSchema describeTableForControllerService(DescribeTableWithControllerService serviceProperties) { String type = serviceProperties.getControllerServiceType(); if (serviceProperties.getControllerServiceType() != null && serviceProperties.getControllerServiceType().equalsIgnoreCase(type)) { Map<String, String> properties = serviceProperties.useEnvironmentProperties() ? nifiControllerServiceProperties.mergeNifiAndEnvProperties(serviceProperties.getControllerServiceDTO().getProperties(), serviceProperties.getControllerServiceName()) : serviceProperties.getControllerServiceDTO().getProperties(); PoolingDataSourceService.DataSourceProperties dataSourceProperties = getDataSourceProperties(properties, serviceProperties); log.info("describing Table {}.{} against Controller Service: {} ({}) with uri of {} ", serviceProperties.getSchemaName(), serviceProperties.getTableName(), serviceProperties.getControllerServiceName(), serviceProperties.getControllerServiceId(), dataSourceProperties.getUrl()); DataSource dataSource = PoolingDataSourceService.getDataSource(dataSourceProperties); DBSchemaParser schemaParser = new DBSchemaParser(dataSource, kerberosHiveConfiguration); return schemaParser.describeTable(serviceProperties.getSchemaName(), serviceProperties.getTableName()); } return null; } private ControllerServiceDTO getControllerService(String serviceId, String serviceName) { ControllerServiceDTO controllerService = nifiControllerServiceProperties.getControllerServiceById(serviceId); if (controllerService == null) { controllerService = nifiControllerServiceProperties.getControllerServiceByName(serviceName); } return controllerService; } public PoolingDataSourceService.DataSourceProperties getDataSourceProperties(Map<String, String> properties, DescribeTableWithControllerService serviceProperties) { String uri = properties.get(serviceProperties.getConnectionStringPropertyKey()); String user = properties.get(serviceProperties.getUserNamePropertyKey()); String password = (serviceProperties.getPassword() != null) ? serviceProperties.getPassword() : properties.get(serviceProperties.getPasswordPropertyKey()); String driverClassName = properties.get(serviceProperties.getDriverClassNamePropertyKey()); if (StringUtils.isBlank(driverClassName)) { driverClassName = nifiControllerServiceProperties.getEnvironmentPropertyValueForControllerService(serviceProperties.getControllerServiceName(), "database_driver_class_name"); } if (StringUtils.isBlank(password)) { password = nifiControllerServiceProperties.getEnvironmentPropertyValueForControllerService(serviceProperties.getControllerServiceName(), "password"); } String validationQuery = nifiControllerServiceProperties.getEnvironmentPropertyValueForControllerService(serviceProperties.getControllerServiceName(), "validationQuery"); if (StringUtils.isBlank(validationQuery)) { //attempt to get it from parsing the connection string validationQuery = parseValidationQueryFromConnectionString(uri); } boolean testOnBorrow = StringUtils.isNotBlank(validationQuery); return new PoolingDataSourceService.DataSourceProperties(user, password, uri, driverClassName, testOnBorrow, validationQuery); } private static class DescribeTableWithControllerServiceBuilder { private String connectionStringPropertyKey; private String userNamePropertyKey; private String passwordPropertyKey; private String driverClassNamePropertyKey; private String controllerServiceType; private String controllerServiceName; private String controllerServiceId; private String tableName; private String schemaName; private ControllerServiceDTO controllerServiceDTO; private String password; private boolean useEnvironmentProperties = true; public DescribeTableWithControllerServiceBuilder(ControllerServiceDTO controllerServiceDTO) { this.controllerServiceDTO = controllerServiceDTO; this.controllerServiceType = controllerServiceDTO != null ? controllerServiceDTO.getType() : null; this.controllerServiceId = controllerServiceDTO != null ? controllerServiceDTO.getId() : null; this.controllerServiceName = controllerServiceDTO != null ? controllerServiceDTO.getName() : null; initializePropertiesFromControllerServiceType(); } public DescribeTableWithControllerServiceBuilder connectionStringPropertyKey(String connectionStringPropertyKey) { this.connectionStringPropertyKey = connectionStringPropertyKey; return this; } public DescribeTableWithControllerServiceBuilder userNamePropertyKey(String userNamePropertyKey) { this.userNamePropertyKey = userNamePropertyKey; return this; } public DescribeTableWithControllerServiceBuilder passwordPropertyKey(String passwordPropertyKey) { this.passwordPropertyKey = passwordPropertyKey; return this; } public DescribeTableWithControllerServiceBuilder driverClassNamePropertyKey(String driverClassNamePropertyKey) { this.driverClassNamePropertyKey = driverClassNamePropertyKey; return this; } public DescribeTableWithControllerServiceBuilder controllerServiceType(String controllerServiceType) { this.controllerServiceType = controllerServiceType; return this; } private void initializePropertiesFromControllerServiceType() { if ("org.apache.nifi.dbcp.DBCPConnectionPool".equalsIgnoreCase(controllerServiceType)) { this.connectionStringPropertyKey = "Database Connection URL"; this.userNamePropertyKey = "Database User"; this.passwordPropertyKey = "Password"; this.driverClassNamePropertyKey = "Database Driver Class Name"; } else if ("com.thinkbiganalytics.nifi.v2.sqoop.StandardSqoopConnectionService".equalsIgnoreCase(controllerServiceType)) { this.connectionStringPropertyKey = "Source Connection String"; this.userNamePropertyKey = "Source User Name"; this.passwordPropertyKey = "Password"; // users will need to add this as a different property to the application.properties file } } public DescribeTableWithControllerServiceBuilder controllerService(ControllerServiceDTO controllerServiceDTO) { this.controllerServiceDTO = controllerServiceDTO; return this; } public DescribeTableWithControllerServiceBuilder controllerServiceName(String controllerServiceName) { this.controllerServiceName = controllerServiceName; return this; } public DescribeTableWithControllerServiceBuilder controllerServiceId(String controllerServiceId) { this.controllerServiceId = controllerServiceId; return this; } public DescribeTableWithControllerServiceBuilder tableName(String tableName) { this.tableName = tableName; return this; } public DescribeTableWithControllerServiceBuilder schemaName(String schemaName) { this.schemaName = schemaName; return this; } public DescribeTableWithControllerServiceBuilder password(String password) { this.password = password; return this; } public DescribeTableWithControllerServiceBuilder useEnvironmentProperties(boolean useEnvironmentProperties) { this.useEnvironmentProperties = useEnvironmentProperties; return this; } public DescribeTableWithControllerService build() { DescribeTableWithControllerService serviceProperties = new DescribeTableWithControllerService(); serviceProperties.setConnectionStringPropertyKey(this.connectionStringPropertyKey); serviceProperties.setControllerServiceName(this.controllerServiceName); serviceProperties.setControllerServiceId(this.controllerServiceId); serviceProperties.setControllerServiceType(this.controllerServiceType); serviceProperties.setSchemaName(schemaName); serviceProperties.setTableName(tableName); serviceProperties.setUserNamePropertyKey(userNamePropertyKey); serviceProperties.setPasswordPropertyKey(passwordPropertyKey); serviceProperties.setControllerServiceDTO(this.controllerServiceDTO); serviceProperties.setDriverClassNamePropertyKey(this.driverClassNamePropertyKey); serviceProperties.setPassword(password); serviceProperties.setUseEnvironmentProperties(useEnvironmentProperties); return serviceProperties; } } public static class DescribeTableWithControllerService { private String connectionStringPropertyKey; private String userNamePropertyKey; private String passwordPropertyKey; private String driverClassNamePropertyKey; private String controllerServiceType; private String controllerServiceName; private String controllerServiceId; private String tableName; private String schemaName; private ControllerServiceDTO controllerServiceDTO; private String password; private boolean useEnvironmentProperties; public String getConnectionStringPropertyKey() { return connectionStringPropertyKey; } public void setConnectionStringPropertyKey(String connectionStringPropertyKey) { this.connectionStringPropertyKey = connectionStringPropertyKey; } public String getUserNamePropertyKey() { return userNamePropertyKey; } public void setUserNamePropertyKey(String userNamePropertyKey) { this.userNamePropertyKey = userNamePropertyKey; } public String getPasswordPropertyKey() { return passwordPropertyKey; } public void setPasswordPropertyKey(String passwordPropertyKey) { this.passwordPropertyKey = passwordPropertyKey; } public String getDriverClassNamePropertyKey() { return driverClassNamePropertyKey; } public void setDriverClassNamePropertyKey(String driverClassNamePropertyKey) { this.driverClassNamePropertyKey = driverClassNamePropertyKey; } public String getControllerServiceType() { return controllerServiceType; } public void setControllerServiceType(String controllerServiceType) { this.controllerServiceType = controllerServiceType; } public String getControllerServiceName() { return controllerServiceName; } public void setControllerServiceName(String controllerServiceName) { this.controllerServiceName = controllerServiceName; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getSchemaName() { return schemaName; } public void setSchemaName(String schemaName) { this.schemaName = schemaName; } public String getControllerServiceId() { return controllerServiceId; } public void setControllerServiceId(String controllerServiceId) { this.controllerServiceId = controllerServiceId; } public ControllerServiceDTO getControllerServiceDTO() { return controllerServiceDTO; } public void setControllerServiceDTO(ControllerServiceDTO controllerServiceDTO) { this.controllerServiceDTO = controllerServiceDTO; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean useEnvironmentProperties() { return useEnvironmentProperties; } public void setUseEnvironmentProperties(boolean useEnvironmentProperties) { this.useEnvironmentProperties = useEnvironmentProperties; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DescribeTableWithControllerService that = (DescribeTableWithControllerService) o; if (connectionStringPropertyKey != null ? !connectionStringPropertyKey.equals(that.connectionStringPropertyKey) : that.connectionStringPropertyKey != null) { return false; } if (controllerServiceName != null ? !controllerServiceName.equals(that.controllerServiceName) : that.controllerServiceName != null) { return false; } if (controllerServiceId != null ? !controllerServiceId.equals(that.controllerServiceId) : that.controllerServiceId != null) { return false; } return schemaName != null ? schemaName.equals(that.schemaName) : that.schemaName == null; } @Override public int hashCode() { int result = connectionStringPropertyKey != null ? connectionStringPropertyKey.hashCode() : 0; result = 31 * result + (controllerServiceName != null ? controllerServiceName.hashCode() : 0); result = 31 * result + (controllerServiceId != null ? controllerServiceId.hashCode() : 0); result = 31 * result + (schemaName != null ? schemaName.hashCode() : 0); return result; } } }