Java tutorial
/** * 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 org.apache.zeppelin.jdbc; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; import static org.apache.commons.lang.StringUtils.isEmpty; import static org.apache.commons.lang.StringUtils.isNotEmpty; import static org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod.KERBEROS; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.PrivilegedExceptionAction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import com.google.common.base.Throwables; import org.apache.commons.dbcp2.ConnectionFactory; import org.apache.commons.dbcp2.DriverManagerConnectionFactory; import org.apache.commons.dbcp2.PoolableConnectionFactory; import org.apache.commons.dbcp2.PoolingDriver; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.thrift.transport.TTransportException; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import org.apache.zeppelin.jdbc.security.JDBCSecurityImpl; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.scheduler.SchedulerFactory; import org.apache.zeppelin.user.UserCredentials; import org.apache.zeppelin.user.UsernamePassword; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; /** * JDBC interpreter for Zeppelin. This interpreter can also be used for accessing HAWQ, * GreenplumDB, MariaDB, MySQL, Postgres and Redshift. * * <ul> * <li>{@code default.url} - JDBC URL to connect to.</li> * <li>{@code default.user} - JDBC user name..</li> * <li>{@code default.password} - JDBC password..</li> * <li>{@code default.driver.name} - JDBC driver name.</li> * <li>{@code common.max.result} - Max number of SQL result to display.</li> * </ul> * * <p> * How to use: <br/> * {@code %jdbc.sql} <br/> * {@code * SELECT store_id, count(*) * FROM retail_demo.order_lineitems_pxf * GROUP BY store_id; * } * </p> */ public class JDBCInterpreter extends Interpreter { private Logger logger = LoggerFactory.getLogger(JDBCInterpreter.class); static final String INTERPRETER_NAME = "jdbc"; static final String COMMON_KEY = "common"; static final String MAX_LINE_KEY = "max_count"; static final int MAX_LINE_DEFAULT = 1000; static final String DEFAULT_KEY = "default"; static final String DRIVER_KEY = "driver"; static final String URL_KEY = "url"; static final String USER_KEY = "user"; static final String PASSWORD_KEY = "password"; static final String JDBC_JCEKS_FILE = "jceks.file"; static final String JDBC_JCEKS_CREDENTIAL_KEY = "jceks.credentialKey"; static final String DOT = "."; private static final char WHITESPACE = ' '; private static final char NEWLINE = '\n'; private static final char TAB = '\t'; private static final String TABLE_MAGIC_TAG = "%table "; private static final String EXPLAIN_PREDICATE = "EXPLAIN "; static final String COMMON_MAX_LINE = COMMON_KEY + DOT + MAX_LINE_KEY; static final String DEFAULT_DRIVER = DEFAULT_KEY + DOT + DRIVER_KEY; static final String DEFAULT_URL = DEFAULT_KEY + DOT + URL_KEY; static final String DEFAULT_USER = DEFAULT_KEY + DOT + USER_KEY; static final String DEFAULT_PASSWORD = DEFAULT_KEY + DOT + PASSWORD_KEY; static final String EMPTY_COLUMN_VALUE = ""; private final String CONCURRENT_EXECUTION_KEY = "zeppelin.jdbc.concurrent.use"; private final String CONCURRENT_EXECUTION_COUNT = "zeppelin.jdbc.concurrent.max_connection"; private final String DBCP_STRING = "jdbc:apache:commons:dbcp:"; private final HashMap<String, Properties> basePropretiesMap; private final HashMap<String, JDBCUserConfigurations> jdbcUserConfigurationsMap; private final Map<String, SqlCompleter> propertyKeySqlCompleterMap; private static final Function<CharSequence, InterpreterCompletion> sequenceToStringTransformer = new Function<CharSequence, InterpreterCompletion>() { public InterpreterCompletion apply(CharSequence seq) { return new InterpreterCompletion(seq.toString(), seq.toString()); } }; private static final List<InterpreterCompletion> NO_COMPLETION = new ArrayList<>(); private int maxLineResults; public JDBCInterpreter(Properties property) { super(property); jdbcUserConfigurationsMap = new HashMap<>(); propertyKeySqlCompleterMap = new HashMap<>(); basePropretiesMap = new HashMap<>(); maxLineResults = MAX_LINE_DEFAULT; } public HashMap<String, Properties> getPropertiesMap() { return basePropretiesMap; } @Override public void open() { for (String propertyKey : property.stringPropertyNames()) { logger.debug("propertyKey: {}", propertyKey); String[] keyValue = propertyKey.split("\\.", 2); if (2 == keyValue.length) { logger.info("key: {}, value: {}", keyValue[0], keyValue[1]); Properties prefixProperties; if (basePropretiesMap.containsKey(keyValue[0])) { prefixProperties = basePropretiesMap.get(keyValue[0]); } else { prefixProperties = new Properties(); basePropretiesMap.put(keyValue[0].trim(), prefixProperties); } prefixProperties.put(keyValue[1].trim(), property.getProperty(propertyKey)); } } Set<String> removeKeySet = new HashSet<>(); for (String key : basePropretiesMap.keySet()) { if (!COMMON_KEY.equals(key)) { Properties properties = basePropretiesMap.get(key); if (!properties.containsKey(DRIVER_KEY) || !properties.containsKey(URL_KEY)) { logger.error("{} will be ignored. {}.{} and {}.{} is mandatory.", key, DRIVER_KEY, key, key, URL_KEY); removeKeySet.add(key); } } } for (String key : removeKeySet) { basePropretiesMap.remove(key); } logger.debug("JDBC PropretiesMap: {}", basePropretiesMap); if (!isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { JDBCSecurityImpl.createSecureConfiguration(property); } for (String propertyKey : basePropretiesMap.keySet()) { propertyKeySqlCompleterMap.put(propertyKey, createSqlCompleter(null)); } setMaxLineResults(); } private void setMaxLineResults() { if (basePropretiesMap.containsKey(COMMON_KEY) && basePropretiesMap.get(COMMON_KEY).containsKey(MAX_LINE_KEY)) { maxLineResults = Integer.valueOf(basePropretiesMap.get(COMMON_KEY).getProperty(MAX_LINE_KEY)); } } private SqlCompleter createSqlCompleter(Connection jdbcConnection) { SqlCompleter completer = new SqlCompleter(); completer.initFromConnection(jdbcConnection, ""); return completer; } private void initStatementMap() { for (JDBCUserConfigurations configurations : jdbcUserConfigurationsMap.values()) { try { configurations.initStatementMap(); } catch (Exception e) { logger.error("Error while closing paragraphIdStatementMap statement...", e); } } } private void initConnectionPoolMap() { for (JDBCUserConfigurations configurations : jdbcUserConfigurationsMap.values()) { try { configurations.initConnectionPoolMap(); } catch (Exception e) { logger.error("Error while closing initConnectionPoolMap...", e); } } } @Override public void close() { try { initStatementMap(); initConnectionPoolMap(); } catch (Exception e) { logger.error("Error while closing...", e); } } private String getEntityName(String replName) { StringBuffer entityName = new StringBuffer(); entityName.append(INTERPRETER_NAME); entityName.append("."); entityName.append(replName); return entityName.toString(); } private String getJDBCDriverName(String user, String propertyKey) { StringBuffer driverName = new StringBuffer(); driverName.append(DBCP_STRING); driverName.append(propertyKey); driverName.append(user); return driverName.toString(); } private boolean existAccountInBaseProperty(String propertyKey) { return basePropretiesMap.get(propertyKey).containsKey(USER_KEY) && basePropretiesMap.get(propertyKey).containsKey(PASSWORD_KEY); } private UsernamePassword getUsernamePassword(InterpreterContext interpreterContext, String replName) { UserCredentials uc = interpreterContext.getAuthenticationInfo().getUserCredentials(); if (uc != null) { return uc.getUsernamePassword(replName); } return null; } public JDBCUserConfigurations getJDBCConfiguration(String user) { JDBCUserConfigurations jdbcUserConfigurations = jdbcUserConfigurationsMap.get(user); if (jdbcUserConfigurations == null) { jdbcUserConfigurations = new JDBCUserConfigurations(); jdbcUserConfigurationsMap.put(user, jdbcUserConfigurations); } return jdbcUserConfigurations; } private void closeDBPool(String user, String propertyKey) throws SQLException { PoolingDriver poolingDriver = getJDBCConfiguration(user).removeDBDriverPool(propertyKey); if (poolingDriver != null) { poolingDriver.closePool(propertyKey + user); } } private void setUserProperty(String propertyKey, InterpreterContext interpreterContext) throws SQLException, IOException { String user = interpreterContext.getAuthenticationInfo().getUser(); JDBCUserConfigurations jdbcUserConfigurations = getJDBCConfiguration(user); if (basePropretiesMap.get(propertyKey).containsKey(USER_KEY) && !basePropretiesMap.get(propertyKey).getProperty(USER_KEY).isEmpty()) { String password = getPassword(basePropretiesMap.get(propertyKey)); if (!isEmpty(password)) { basePropretiesMap.get(propertyKey).setProperty(PASSWORD_KEY, password); } } jdbcUserConfigurations.setPropertyMap(propertyKey, basePropretiesMap.get(propertyKey)); if (existAccountInBaseProperty(propertyKey)) { return; } jdbcUserConfigurations.cleanUserProperty(propertyKey); UsernamePassword usernamePassword = getUsernamePassword(interpreterContext, getEntityName(interpreterContext.getReplName())); if (usernamePassword != null) { jdbcUserConfigurations.setUserProperty(propertyKey, usernamePassword); } else { closeDBPool(user, propertyKey); } } private void createConnectionPool(String url, String user, String propertyKey, Properties properties) throws SQLException, ClassNotFoundException { ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(url, properties); PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); ObjectPool connectionPool = new GenericObjectPool(poolableConnectionFactory); poolableConnectionFactory.setPool(connectionPool); Class.forName(properties.getProperty(DRIVER_KEY)); PoolingDriver driver = new PoolingDriver(); driver.registerPool(propertyKey + user, connectionPool); getJDBCConfiguration(user).saveDBDriverPool(propertyKey, driver); } private Connection getConnectionFromPool(String url, String user, String propertyKey, Properties properties) throws SQLException, ClassNotFoundException { String jdbcDriver = getJDBCDriverName(user, propertyKey); if (!getJDBCConfiguration(user).isConnectionInDBDriverPool(propertyKey)) { createConnectionPool(url, user, propertyKey, properties); } return DriverManager.getConnection(jdbcDriver); } public Connection getConnection(String propertyKey, InterpreterContext interpreterContext) throws ClassNotFoundException, SQLException, InterpreterException, IOException { final String user = interpreterContext.getAuthenticationInfo().getUser(); Connection connection; if (propertyKey == null || basePropretiesMap.get(propertyKey) == null) { return null; } JDBCUserConfigurations jdbcUserConfigurations = getJDBCConfiguration(user); setUserProperty(propertyKey, interpreterContext); final Properties properties = jdbcUserConfigurations.getPropertyMap(propertyKey); final String url = properties.getProperty(URL_KEY); if (isEmpty(property.getProperty("zeppelin.jdbc.auth.type"))) { connection = getConnectionFromPool(url, user, propertyKey, properties); } else { UserGroupInformation.AuthenticationMethod authType = JDBCSecurityImpl.getAuthtype(property); switch (authType) { case KERBEROS: if (user == null) { connection = getConnectionFromPool(url, user, propertyKey, properties); } else { if (url.trim().startsWith("jdbc:hive")) { StringBuilder connectionUrl = new StringBuilder(url); Integer lastIndexOfUrl = connectionUrl.indexOf("?"); if (lastIndexOfUrl == -1) { lastIndexOfUrl = connectionUrl.length(); } connectionUrl.insert(lastIndexOfUrl, ";hive.server2.proxy.user=" + user + ";"); connection = getConnectionFromPool(connectionUrl.toString(), user, propertyKey, properties); } else { UserGroupInformation ugi = null; try { ugi = UserGroupInformation.createProxyUser(user, UserGroupInformation.getCurrentUser()); } catch (Exception e) { logger.error("Error in createProxyUser", e); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(e.getMessage()).append("\n"); stringBuilder.append(e.getCause()); throw new InterpreterException(stringBuilder.toString()); } final String poolKey = propertyKey; try { connection = ugi.doAs(new PrivilegedExceptionAction<Connection>() { @Override public Connection run() throws Exception { return getConnectionFromPool(url, user, poolKey, properties); } }); } catch (Exception e) { logger.error("Error in doAs", e); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(e.getMessage()).append("\n"); stringBuilder.append(e.getCause()); throw new InterpreterException(stringBuilder.toString()); } } } break; default: connection = getConnectionFromPool(url, user, propertyKey, properties); } } propertyKeySqlCompleterMap.put(propertyKey, createSqlCompleter(connection)); return connection; } private String getPassword(Properties properties) throws IOException { if (isNotEmpty(properties.getProperty(PASSWORD_KEY))) { return properties.getProperty(PASSWORD_KEY); } else if (isNotEmpty(properties.getProperty(JDBC_JCEKS_FILE)) && isNotEmpty(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY))) { try { Configuration configuration = new Configuration(); configuration.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, properties.getProperty(JDBC_JCEKS_FILE)); CredentialProvider provider = CredentialProviderFactory.getProviders(configuration).get(0); CredentialProvider.CredentialEntry credEntry = provider .getCredentialEntry(properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY)); if (credEntry != null) { return new String(credEntry.getCredential()); } else { throw new InterpreterException("Failed to retrieve password from JCEKS from key: " + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY)); } } catch (Exception e) { logger.error("Failed to retrieve password from JCEKS \n" + "For file: " + properties.getProperty(JDBC_JCEKS_FILE) + "\nFor key: " + properties.getProperty(JDBC_JCEKS_CREDENTIAL_KEY), e); throw e; } } return null; } private String getResults(ResultSet resultSet, boolean isTableType) throws SQLException { ResultSetMetaData md = resultSet.getMetaData(); StringBuilder msg; if (isTableType) { msg = new StringBuilder(TABLE_MAGIC_TAG); } else { msg = new StringBuilder(); } for (int i = 1; i < md.getColumnCount() + 1; i++) { if (i > 1) { msg.append(TAB); } msg.append(replaceReservedChars(md.getColumnName(i))); } msg.append(NEWLINE); int displayRowCount = 0; while (resultSet.next() && displayRowCount < getMaxResult()) { for (int i = 1; i < md.getColumnCount() + 1; i++) { Object resultObject; String resultValue; resultObject = resultSet.getObject(i); if (resultObject == null) { resultValue = "null"; } else { resultValue = resultSet.getString(i); } msg.append(replaceReservedChars(resultValue)); if (i != md.getColumnCount()) { msg.append(TAB); } } msg.append(NEWLINE); displayRowCount++; } return msg.toString(); } private boolean isDDLCommand(int updatedCount, int columnCount) throws SQLException { return updatedCount < 0 && columnCount <= 0 ? true : false; } /* inspired from https://github.com/postgres/pgadmin3/blob/794527d97e2e3b01399954f3b79c8e2585b908dd/ pgadmin/dlg/dlgProperty.cpp#L999-L1045 */ protected ArrayList<String> splitSqlQueries(String sql) { ArrayList<String> queries = new ArrayList<>(); StringBuilder query = new StringBuilder(); Character character; Boolean antiSlash = false; Boolean quoteString = false; Boolean doubleQuoteString = false; for (int item = 0; item < sql.length(); item++) { character = sql.charAt(item); if (character.equals('\\')) { antiSlash = true; } if (character.equals('\'')) { if (antiSlash) { antiSlash = false; } else if (quoteString) { quoteString = false; } else if (!doubleQuoteString) { quoteString = true; } } if (character.equals('"')) { if (antiSlash) { antiSlash = false; } else if (doubleQuoteString) { doubleQuoteString = false; } else if (!quoteString) { doubleQuoteString = true; } } if (character.equals(';') && !antiSlash && !quoteString && !doubleQuoteString) { queries.add(query.toString()); query = new StringBuilder(); } else if (item == sql.length() - 1) { query.append(character); queries.add(query.toString()); } else { query.append(character); } } return queries; } private InterpreterResult executeSql(String propertyKey, String sql, InterpreterContext interpreterContext) { Connection connection; Statement statement; ResultSet resultSet = null; String paragraphId = interpreterContext.getParagraphId(); String user = interpreterContext.getAuthenticationInfo().getUser(); InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS); try { connection = getConnection(propertyKey, interpreterContext); if (connection == null) { return new InterpreterResult(Code.ERROR, "Prefix not found."); } ArrayList<String> multipleSqlArray = splitSqlQueries(sql); for (int i = 0; i < multipleSqlArray.size(); i++) { String sqlToExecute = multipleSqlArray.get(i); statement = connection.createStatement(); if (statement == null) { return new InterpreterResult(Code.ERROR, "Prefix not found."); } try { getJDBCConfiguration(user).saveStatement(paragraphId, statement); boolean isResultSetAvailable = statement.execute(sqlToExecute); getJDBCConfiguration(user).setConnectionInDBDriverPoolSuccessful(propertyKey); if (isResultSetAvailable) { resultSet = statement.getResultSet(); // Regards that the command is DDL. if (isDDLCommand(statement.getUpdateCount(), resultSet.getMetaData().getColumnCount())) { interpreterResult.add(InterpreterResult.Type.TEXT, "Query executed successfully."); } else { interpreterResult.add( getResults(resultSet, !containsIgnoreCase(sqlToExecute, EXPLAIN_PREDICATE))); } } else { // Response contains either an update count or there are no results. int updateCount = statement.getUpdateCount(); interpreterResult.add(InterpreterResult.Type.TEXT, "Query executed successfully. Affected rows : " + updateCount); } } finally { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { /*ignored*/ } } if (statement != null) { try { statement.close(); } catch (SQLException e) { /*ignored*/ } } } } //In case user ran an insert/update/upsert statement if (connection != null) { try { if (!connection.getAutoCommit()) { connection.commit(); } connection.close(); } catch (SQLException e) { /*ignored*/ } } getJDBCConfiguration(user).removeStatement(paragraphId); } catch (Exception e) { if (e.getCause() instanceof TTransportException && Throwables.getStackTraceAsString(e).contains("GSS") && getJDBCConfiguration(user).isConnectionInDBDriverPoolSuccessful(propertyKey)) { return reLoginFromKeytab(propertyKey, sql, interpreterContext, interpreterResult); } else { logger.error("Cannot run " + sql, e); String errorMsg = Throwables.getStackTraceAsString(e); try { closeDBPool(user, propertyKey); } catch (SQLException e1) { logger.error("Cannot close DBPool for user, propertyKey: " + user + propertyKey, e1); } interpreterResult.add(errorMsg); return new InterpreterResult(Code.ERROR, interpreterResult.message()); } } return interpreterResult; } private InterpreterResult reLoginFromKeytab(String propertyKey, String sql, InterpreterContext interpreterContext, InterpreterResult interpreterResult) { String user = interpreterContext.getAuthenticationInfo().getUser(); try { closeDBPool(user, propertyKey); } catch (SQLException e) { logger.error("Error, could not close DB pool in reLoginFromKeytab ", e); } UserGroupInformation.AuthenticationMethod authType = JDBCSecurityImpl.getAuthtype(property); if (authType.equals(KERBEROS)) { try { if (UserGroupInformation.isLoginKeytabBased()) { UserGroupInformation.getLoginUser().reloginFromKeytab(); } else if (UserGroupInformation.isLoginTicketBased()) { UserGroupInformation.getLoginUser().reloginFromTicketCache(); } } catch (IOException e) { logger.error("Cannot reloginFromKeytab " + sql, e); interpreterResult.add(e.getMessage()); return new InterpreterResult(Code.ERROR, interpreterResult.message()); } } return executeSql(propertyKey, sql, interpreterContext); } /** * For %table response replace Tab and Newline characters from the content. */ private String replaceReservedChars(String str) { if (str == null) { return EMPTY_COLUMN_VALUE; } return str.replace(TAB, WHITESPACE).replace(NEWLINE, WHITESPACE); } @Override public InterpreterResult interpret(String cmd, InterpreterContext contextInterpreter) { logger.info("Run SQL command '{}'", cmd); String propertyKey = getPropertyKey(cmd); if (null != propertyKey && !propertyKey.equals(DEFAULT_KEY)) { cmd = cmd.substring(propertyKey.length() + 2); } cmd = cmd.trim(); logger.info("PropertyKey: {}, SQL command: '{}'", propertyKey, cmd); return executeSql(propertyKey, cmd, contextInterpreter); } @Override public void cancel(InterpreterContext context) { logger.info("Cancel current query statement."); String paragraphId = context.getParagraphId(); JDBCUserConfigurations jdbcUserConfigurations = getJDBCConfiguration( context.getAuthenticationInfo().getUser()); try { jdbcUserConfigurations.cancelStatement(paragraphId); } catch (SQLException e) { logger.error("Error while cancelling...", e); } } public String getPropertyKey(String cmd) { boolean firstLineIndex = cmd.startsWith("("); if (firstLineIndex) { int configStartIndex = cmd.indexOf("("); int configLastIndex = cmd.indexOf(")"); if (configStartIndex != -1 && configLastIndex != -1) { return cmd.substring(configStartIndex + 1, configLastIndex); } else { return null; } } else { return DEFAULT_KEY; } } @Override public FormType getFormType() { return FormType.SIMPLE; } @Override public int getProgress(InterpreterContext context) { return 0; } @Override public Scheduler getScheduler() { String schedulerName = JDBCInterpreter.class.getName() + this.hashCode(); return isConcurrentExecution() ? SchedulerFactory.singleton().createOrGetParallelScheduler(schedulerName, getMaxConcurrentConnection()) : SchedulerFactory.singleton().createOrGetFIFOScheduler(schedulerName); } @Override public List<InterpreterCompletion> completion(String buf, int cursor) { List<CharSequence> candidates = new ArrayList<>(); SqlCompleter sqlCompleter = propertyKeySqlCompleterMap.get(getPropertyKey(buf)); // It's strange but here cursor comes with additional +1 (even if buf is "" cursor = 1) if (sqlCompleter != null && sqlCompleter.complete(buf, cursor - 1, candidates) >= 0) { List<InterpreterCompletion> completion; completion = Lists.transform(candidates, sequenceToStringTransformer); return completion; } else { return NO_COMPLETION; } } public int getMaxResult() { return maxLineResults; } boolean isConcurrentExecution() { return Boolean.valueOf(getProperty(CONCURRENT_EXECUTION_KEY)); } int getMaxConcurrentConnection() { try { return Integer.valueOf(getProperty(CONCURRENT_EXECUTION_COUNT)); } catch (Exception e) { return 10; } } }