/* * 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.cassandra.cli; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.nio.charset.CharacterCodingException; import java.util.*; import org.apache.cassandra.auth.IAuthenticator; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.thrift.*; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.transport.TTransport; import jline.ConsoleReader; import jline.History; /** * Cassandra Command Line Interface (CLI) Main */ public class CliMain { public final static String OLD_HISTORYFILE = ".cassandra.history"; public final static String HISTORYFILE = "cli.history"; private static TTransport transport = null; private static Cassandra.Client thriftClient = null; public static final CliSessionState sessionState = new CliSessionState(); private static CliClient cliClient; private static final CliCompleter completer = new CliCompleter(); private static int lineNumber = 1; /** * Establish a thrift connection to cassandra instance * * @param server - hostname or IP of the server * @param port - Thrift port number */ public static void connect(String server, int port) { if (transport != null) transport.close(); try { transport = sessionState.transportFactory.openTransport(server, port); } catch (Exception e) { e.printStackTrace(sessionState.err); String error = (e.getCause() == null) ? e.getMessage() : e.getCause().getMessage(); throw new RuntimeException("Exception connecting to " + server + "/" + port + ". Reason: " + error + "."); } TBinaryProtocol binaryProtocol = new TBinaryProtocol(transport, true, true); thriftClient = new Cassandra.Client(binaryProtocol); cliClient = new CliClient(sessionState, thriftClient); if ((sessionState.username != null) && (sessionState.password != null)) { // Authenticate Map<String, String> credentials = new HashMap<String, String>(); credentials.put(IAuthenticator.USERNAME_KEY, sessionState.username); credentials.put(IAuthenticator.PASSWORD_KEY, sessionState.password); AuthenticationRequest authRequest = new AuthenticationRequest(credentials); try { thriftClient.login(authRequest); cliClient.setUsername(sessionState.username); } catch (AuthenticationException e) { thriftClient = null; sessionState.err.println("Exception during authentication to the cassandra node, " + "Verify the keyspace exists, and that you are using the correct credentials."); return; } catch (AuthorizationException e) { thriftClient = null; sessionState.err.println("You are not authorized to use keyspace: " + sessionState.keyspace); return; } catch (TException e) { thriftClient = null; sessionState.err.println("Login failure. Did you specify 'keyspace', 'username' and 'password'?"); return; } } if (sessionState.keyspace != null) { try { sessionState.keyspace = CliCompiler.getKeySpace(sessionState.keyspace, thriftClient.describe_keyspaces());; thriftClient.set_keyspace(sessionState.keyspace); cliClient.setKeySpace(sessionState.keyspace); updateCompletor(CliUtils.getCfNamesByKeySpace(cliClient.getKSMetaData(sessionState.keyspace))); } catch (InvalidRequestException | NotFoundException e) { sessionState.err.println("Keyspace " + sessionState.keyspace + " not found"); return; } catch (TException e) { sessionState.err.println("Did you specify 'keyspace'?"); return; } } // Lookup the cluster name, this is to make it clear which cluster the user is connected to String clusterName; try { clusterName = thriftClient.describe_cluster_name(); } catch (Exception e) { JVMStabilityInspector.inspectThrowable(e); sessionState.err.println("Exception retrieving information about the cassandra node, check you have connected to the thrift port."); e.printStackTrace(sessionState.err); return; } sessionState.out.printf("Connected to: \"%s\" on %s/%d%n", clusterName, server, port); } /** * Disconnect thrift connection to cassandra instance */ public static void disconnect() { if (transport != null) { transport.close(); transport = null; } } /** * Checks whether the thrift client is connected. * @return boolean - true when connected, false otherwise */ public static boolean isConnected() { if (thriftClient == null) { sessionState.out.println("Not connected to a cassandra instance."); return false; } return true; } public static void updateCompletor(Set<String> candidates) { Set<String> actions = new HashSet<String>(); for (String cf : candidates) { for (String cmd : completer.getKeyspaceCommands()) actions.add(String.format("%s %s", cmd, cf)); } String[] strs = Arrays.copyOf(actions.toArray(), actions.toArray().length, String[].class); completer.setCandidateStrings(strs); } public static void processStatement(String query) throws CharacterCodingException, TException, NotFoundException, InvalidRequestException, NoSuchFieldException, UnavailableException, IllegalAccessException, InstantiationException { cliClient.executeCLIStatement(query); } public static void processStatementInteractive(String query) { try { cliClient.executeCLIStatement(query); } catch (Exception e) { String errorTemplate = sessionState.inFileMode() ? "Line " + lineNumber + " => " : ""; Throwable exception = (e.getCause() == null) ? e : e.getCause(); String message = (exception instanceof InvalidRequestException) ? ((InvalidRequestException) exception).getWhy() : e.getMessage(); sessionState.err.println(errorTemplate + message); if (sessionState.debug || !(e instanceof RuntimeException)) e.printStackTrace(sessionState.err); if (sessionState.batch || sessionState.inFileMode()) { System.exit(4); } } finally { lineNumber++; } } public static void main(String args[]) throws IOException { // process command line arguments CliOptions cliOptions = new CliOptions(); cliOptions.processArgs(sessionState, args); // connect to cassandra server if host argument specified. if (sessionState.hostName != null) { try { connect(sessionState.hostName, sessionState.thriftPort); } catch (RuntimeException e) { sessionState.err.println(e.getMessage()); } } if ( cliClient == null ) { // Connection parameter was either invalid or not present. // User must connect explicitly using the "connect" CLI statement. cliClient = new CliClient(sessionState, null); } // load statements from file and process them if (sessionState.inFileMode()) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(sessionState.filename)); evaluateFileStatements(reader); } catch (IOException e) { sessionState.err.println(e.getMessage()); System.exit(1); } finally { FileUtils.closeQuietly(reader); } return; } ConsoleReader reader = new ConsoleReader(); if (!sessionState.batch) { reader.addCompletor(completer); reader.setBellEnabled(false); File historyFile = handleHistoryFiles(); try { History history = new History(historyFile); reader.setHistory(history); } catch (IOException exp) { sessionState.err.printf("Unable to open %s for writing", historyFile.getAbsolutePath()); } } else if (!sessionState.verbose) // if in batch mode but no verbose flag { sessionState.out.close(); } cliClient.printBanner(); String prompt; String line = ""; String currentStatement = ""; boolean inCompoundStatement = false; while (line != null) { prompt = (inCompoundStatement) ? "...\t" : getPrompt(cliClient); try { line = reader.readLine(prompt); } catch (IOException e) { // retry on I/O Exception } if (line == null) return; line = line.trim(); // skipping empty and comment lines if (line.isEmpty() || line.startsWith("--")) continue; currentStatement += line; if (line.endsWith(";") || line.equals("?")) { processStatementInteractive(currentStatement); currentStatement = ""; inCompoundStatement = false; } else { currentStatement += " "; // ready for new line inCompoundStatement = true; } } } private static File handleHistoryFiles() { File outputDir = FBUtilities.getToolsOutputDirectory(); File historyFile = new File(outputDir, HISTORYFILE); File oldHistoryFile = new File(System.getProperty("user.home"), OLD_HISTORYFILE); if(oldHistoryFile.exists()) FileUtils.renameWithConfirm(oldHistoryFile, historyFile); return historyFile; } private static void evaluateFileStatements(BufferedReader reader) throws IOException { String line; String currentStatement = ""; boolean commentedBlock = false; while ((line = reader.readLine()) != null) { line = line.trim(); // skipping empty and comment lines if (line.isEmpty() || line.startsWith("--")) continue; if (line.startsWith("/*")) commentedBlock = true; if (line.startsWith("*/") || line.endsWith("*/")) { commentedBlock = false; continue; } if (commentedBlock) // skip commented lines continue; currentStatement += line; if (line.endsWith(";")) { processStatementInteractive(currentStatement); currentStatement = ""; } else { currentStatement += " "; // ready for new line } } } /** * Returns prompt for current connection * @param client - currently connected client * @return String - prompt with username and keyspace (if any) */ private static String getPrompt(CliClient client) { return "[" + client.getUsername() + "@" + client.getKeySpace() + "] "; } }