/* * Licensed to 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 gobblin.util; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.Enumeration; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import gobblin.password.PasswordManager; /** * A class for managing a Hive JDBC connection. * * @author Ziyang Liu */ public class HiveJdbcConnector implements Closeable { public static final String HIVESERVER_VERSION = "hiveserver.version"; public static final String DEFAULT_HIVESERVER_VERSION = "2"; public static final String HIVESERVER_CONNECTION_STRING = "hiveserver.connection.string"; public static final String HIVESERVER_URL = "hiveserver.url"; public static final String HIVESERVER_USER = "hiveserver.user"; public static final String HIVESERVER_PASSWORD = "hiveserver.password"; public static final String HIVESITE_DIR = "hivesite.dir"; public static final String HIVE_EXECUTION_SIMULATE = "hive.execution.simulate"; private static final String HIVE_JDBC_DRIVER_NAME = "org.apache.hadoop.hive.jdbc.HiveDriver"; private static final String HIVE2_JDBC_DRIVER_NAME = "org.apache.hive.jdbc.HiveDriver"; private static final String HIVE_EMBEDDED_CONNECTION_STRING = "jdbc:hive://"; private static final String HIVE2_EMBEDDED_CONNECTION_STRING = "jdbc:hive2://"; private static final String HIVE_CONFIG_KEY_PREFIX = "hive."; private static final int MAX_OUTPUT_STMT_LENGTH = 1000; private static final Logger LOG = LoggerFactory.getLogger(HiveJdbcConnector.class); // Connection to the Hive DB private Connection conn; // Re-usable Statement private Statement stmt; private int hiveServerVersion; private boolean isSimulate; private HiveJdbcConnector() { this(false); } private HiveJdbcConnector(boolean isSimulate) { this.isSimulate = isSimulate; } /** * Create a new {@link HiveJdbcConnector} using the specified Hive server version. * @param hiveServerVersion is the Hive server version to use * @return a HiveJdbcConnector with the specified hiveServerVersion * @throws SQLException */ @SuppressWarnings("resource") public static HiveJdbcConnector newEmbeddedConnector(int hiveServerVersion) throws SQLException { return new HiveJdbcConnector().withHiveServerVersion(hiveServerVersion).withHiveEmbeddedConnection(); } /** * Create a new {@link HiveJdbcConnector} based on the configs in a {@link Properties} object * @param compactRunProps contains the configuration keys to construct the {@link HiveJdbcConnector} * @throws SQLException if there is a problem setting up the JDBC connection * @return */ public static HiveJdbcConnector newConnectorWithProps(Properties compactRunProps) throws SQLException { boolean isSimulate = Boolean.valueOf(compactRunProps.getProperty(HIVE_EXECUTION_SIMULATE)); HiveJdbcConnector hiveJdbcConnector = new HiveJdbcConnector(isSimulate); // Set the Hive Server type hiveJdbcConnector.withHiveServerVersion( Integer.parseInt(compactRunProps.getProperty(HIVESERVER_VERSION, DEFAULT_HIVESERVER_VERSION))); // Add the Hive Site Dir to the classpath if (compactRunProps.containsKey(HIVESITE_DIR)) { HiveJdbcConnector.addHiveSiteDirToClasspath(compactRunProps.getProperty(HIVESITE_DIR)); } // Set and create the Hive JDBC connection if (compactRunProps.containsKey(HIVESERVER_CONNECTION_STRING)) { hiveJdbcConnector.withHiveConnectionFromUrl(compactRunProps.getProperty(HIVESERVER_CONNECTION_STRING)); } else if (compactRunProps.containsKey(HIVESERVER_URL)) { hiveJdbcConnector.withHiveConnectionFromUrlUserPassword(compactRunProps.getProperty(HIVESERVER_URL), compactRunProps.getProperty(HIVESERVER_USER), PasswordManager.getInstance(compactRunProps).readPassword(compactRunProps.getProperty(HIVESERVER_PASSWORD))); } else { hiveJdbcConnector.withHiveEmbeddedConnection(); } // Set Hive properties hiveJdbcConnector.setHiveProperties(compactRunProps); return hiveJdbcConnector; } /** * Set the {@link HiveJdbcConnector#hiveServerVersion}. The hiveServerVersion must be either 1 or 2. * @param hiveServerVersion * @return */ private HiveJdbcConnector withHiveServerVersion(int hiveServerVersion) { try { if (hiveServerVersion == 1) { Class.forName(HIVE_JDBC_DRIVER_NAME); } else if (hiveServerVersion == 2) { Class.forName(HIVE2_JDBC_DRIVER_NAME); } else { throw new RuntimeException(hiveServerVersion + " is not a valid HiveServer version."); } } catch (ClassNotFoundException e) { throw new RuntimeException("Cannot find a suitable driver of HiveServer version " + hiveServerVersion + ".", e); } this.hiveServerVersion = hiveServerVersion; return this; } /** * Set the {@link HiveJdbcConnector#conn} based on the URL given to the method * @param hiveServerUrl is the URL to connect to * @throws SQLException if there is a problem connectiong to the URL * @return */ private HiveJdbcConnector withHiveConnectionFromUrl(String hiveServerUrl) throws SQLException { this.conn = DriverManager.getConnection(hiveServerUrl); this.stmt = this.conn.createStatement(); return this; } /** * Set the {@link HiveJdbcConnector#conn} based on the URL, user, and password given to the method * @param hiveServerUrl is the URL to connect to * @param hiveServerUser is the username to connect with * @param hiveServerPassword is the password to authenticate with when connecting to the URL * @throws SQLException if there is a problem connecting to the URL * @return */ private HiveJdbcConnector withHiveConnectionFromUrlUserPassword(String hiveServerUrl, String hiveServerUser, String hiveServerPassword) throws SQLException { this.conn = DriverManager.getConnection(hiveServerUrl, hiveServerUser, hiveServerPassword); this.stmt = this.conn.createStatement(); return this; } /** * Set the {@link HiveJdbcConnector#conn} based on {@link HiveJdbcConnector#hiveServerVersion} * @throws SQLException if there is a problem connection to the URL * @return */ private HiveJdbcConnector withHiveEmbeddedConnection() throws SQLException { if (this.hiveServerVersion == 1) { this.conn = DriverManager.getConnection(HIVE_EMBEDDED_CONNECTION_STRING); } else { this.conn = DriverManager.getConnection(HIVE2_EMBEDDED_CONNECTION_STRING); } this.stmt = this.conn.createStatement(); return this; } /** * Helper method to add the directory containing the hive-site.xml file to the classpath * @param hiveSiteDir is the path to to the folder containing the hive-site.xml file */ private static void addHiveSiteDirToClasspath(String hiveSiteDir) { LOG.info("Adding " + hiveSiteDir + " to CLASSPATH"); File f = new File(hiveSiteDir); try { URL u = f.toURI().toURL(); URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class<URLClassLoader> urlClass = URLClassLoader.class; Method method = urlClass.getDeclaredMethod("addURL", new Class[] { URL.class }); method.setAccessible(true); method.invoke(urlClassLoader, new Object[] { u }); } catch (ReflectiveOperationException | IOException e) { throw new RuntimeException("Unable to add hive.site.dir to CLASSPATH", e); } } /** * Helper method that executes a series of "set ?=?" queries for the Hive connection in {@link HiveJdbcConnector#conn}. * @param props specifies which set methods to run. For example, if the config contains "hive.mapred.min.split.size=100" * then "set mapred.min.split.size=100" will be executed. * @throws SQLException is thrown if there is a problem executing the "set" queries */ private void setHiveProperties(Properties props) throws SQLException { Preconditions.checkNotNull(this.conn, "The Hive connection must be set before any queries can be run"); try (PreparedStatement preparedStatement = this.conn.prepareStatement("set ?=?")) { Enumeration<?> enumeration = props.propertyNames(); while (enumeration.hasMoreElements()) { String propertyName = (String) enumeration.nextElement(); if (propertyName.startsWith(HIVE_CONFIG_KEY_PREFIX)) { preparedStatement.setString(1, propertyName); preparedStatement.setString(2, props.getProperty(propertyName)); preparedStatement.execute(); } } } } /*** * Executes the given SQL statements. * * @param statements SQL statements to be executed. * @throws SQLException if any issue in executing any statement. */ public void executeStatements(String... statements) throws SQLException { Preconditions.checkNotNull(this.conn, "The Hive connection must be set before any queries can be run"); for (String statement : statements) { if (isSimulate) { LOG.info("[SIMULATE MODE] STATEMENT NOT RUN: " + choppedStatement(statement)); } else { LOG.info("RUNNING STATEMENT: " + choppedStatement(statement)); this.stmt.execute(statement); } } } private static String choppedStatement(String statement) { if (statement.length() <= MAX_OUTPUT_STMT_LENGTH) { return statement; } return statement.substring(0, MAX_OUTPUT_STMT_LENGTH) + "...... (" + (statement.length() - MAX_OUTPUT_STMT_LENGTH) + " characters ommitted)"; } public Connection getConnection() { return this.conn; } @Override public void close() throws IOException { if (this.stmt != null) { try { this.stmt.close(); } catch (SQLException e) { LOG.error("Failed to close JDBC statement object", e); } } if (this.conn != null) { try { this.conn.close(); } catch (SQLException e) { LOG.error("Failed to close JDBC connection", e); } } } }