| /* |
| * 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.transport; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| import java.util.*; |
| |
| import com.google.common.base.Splitter; |
| |
| import org.apache.cassandra.auth.PasswordAuthenticator; |
| import org.apache.cassandra.config.DatabaseDescriptor; |
| import org.apache.cassandra.cql3.QueryOptions; |
| import org.apache.cassandra.db.ConsistencyLevel; |
| import org.apache.cassandra.db.marshal.Int32Type; |
| import org.apache.cassandra.db.marshal.UTF8Type; |
| import org.apache.cassandra.transport.messages.*; |
| import org.apache.cassandra.utils.Hex; |
| import org.apache.cassandra.utils.JVMStabilityInspector; |
| import org.apache.cassandra.utils.MD5Digest; |
| |
| import static org.apache.cassandra.config.EncryptionOptions.ClientEncryptionOptions; |
| |
| public class Client extends SimpleClient |
| { |
| private final SimpleEventHandler eventHandler = new SimpleEventHandler(); |
| |
| public Client(String host, int port, ProtocolVersion version, ClientEncryptionOptions encryptionOptions) |
| { |
| super(host, port, version, encryptionOptions); |
| setEventHandler(eventHandler); |
| } |
| |
| public void run() throws IOException |
| { |
| // Start the connection attempt. |
| System.out.print("Connecting..."); |
| establishConnection(); |
| System.out.println(); |
| |
| // Read commands from the stdin. |
| BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); |
| for (;;) |
| { |
| Event event; |
| while ((event = eventHandler.queue.poll()) != null) |
| { |
| System.out.println("<< " + event); |
| } |
| |
| System.out.print(">> "); |
| System.out.flush(); |
| String line = in.readLine(); |
| if (line == null) |
| { |
| break; |
| } |
| Message.Request req = parseLine(line.trim()); |
| if (req == null) |
| { |
| System.out.println("! Error parsing line."); |
| continue; |
| } |
| |
| try |
| { |
| Message.Response resp = execute(req); |
| System.out.println("-> " + resp); |
| } |
| catch (Exception e) |
| { |
| JVMStabilityInspector.inspectThrowable(e); |
| System.err.println("ERROR: " + e.getMessage()); |
| } |
| } |
| |
| close(); |
| } |
| |
| private Message.Request parseLine(String line) |
| { |
| Splitter splitter = Splitter.on(' ').trimResults().omitEmptyStrings(); |
| Iterator<String> iter = splitter.split(line).iterator(); |
| if (!iter.hasNext()) |
| return null; |
| String msgType = iter.next().toUpperCase(); |
| if (msgType.equals("STARTUP")) |
| { |
| Map<String, String> options = new HashMap<String, String>(); |
| options.put(StartupMessage.CQL_VERSION, "3.0.0"); |
| while (iter.hasNext()) |
| { |
| String next = iter.next(); |
| if (next.toLowerCase().equals("snappy")) |
| { |
| options.put(StartupMessage.COMPRESSION, "snappy"); |
| connection.setCompressor(FrameCompressor.SnappyCompressor.instance); |
| } |
| } |
| return new StartupMessage(options); |
| } |
| else if (msgType.equals("QUERY")) |
| { |
| line = line.substring(6); |
| // Ugly hack to allow setting a page size, but that's playground code anyway |
| String query = line; |
| int pageSize = -1; |
| if (line.matches(".+ !\\d+$")) |
| { |
| int idx = line.lastIndexOf('!'); |
| query = line.substring(0, idx-1); |
| try |
| { |
| pageSize = Integer.parseInt(line.substring(idx+1, line.length())); |
| } |
| catch (NumberFormatException e) |
| { |
| return null; |
| } |
| } |
| return new QueryMessage(query, QueryOptions.create(ConsistencyLevel.ONE, Collections.<ByteBuffer>emptyList(), false, pageSize, null, null, version)); |
| } |
| else if (msgType.equals("PREPARE")) |
| { |
| String query = line.substring(8); |
| return new PrepareMessage(query); |
| } |
| else if (msgType.equals("EXECUTE")) |
| { |
| try |
| { |
| byte[] id = Hex.hexToBytes(iter.next()); |
| List<ByteBuffer> values = new ArrayList<ByteBuffer>(); |
| while(iter.hasNext()) |
| { |
| String next = iter.next(); |
| ByteBuffer bb; |
| try |
| { |
| int v = Integer.parseInt(next); |
| bb = Int32Type.instance.decompose(v); |
| } |
| catch (NumberFormatException e) |
| { |
| bb = UTF8Type.instance.decompose(next); |
| } |
| values.add(bb); |
| } |
| return new ExecuteMessage(MD5Digest.wrap(id), QueryOptions.forInternalCalls(ConsistencyLevel.ONE, values)); |
| } |
| catch (Exception e) |
| { |
| return null; |
| } |
| } |
| else if (msgType.equals("OPTIONS")) |
| { |
| return new OptionsMessage(); |
| } |
| else if (msgType.equals("CREDENTIALS")) |
| { |
| System.err.println("[WARN] CREDENTIALS command is deprecated, use AUTHENTICATE instead"); |
| CredentialsMessage msg = new CredentialsMessage(); |
| msg.credentials.putAll(readCredentials(iter)); |
| return msg; |
| } |
| else if (msgType.equals("AUTHENTICATE")) |
| { |
| Map<String, String> credentials = readCredentials(iter); |
| if(!credentials.containsKey(PasswordAuthenticator.USERNAME_KEY) || !credentials.containsKey(PasswordAuthenticator.PASSWORD_KEY)) |
| { |
| System.err.println("[ERROR] Authentication requires both 'username' and 'password'"); |
| return null; |
| } |
| return new AuthResponse(encodeCredentialsForSasl(credentials)); |
| } |
| else if (msgType.equals("REGISTER")) |
| { |
| String type = line.substring(9).toUpperCase(); |
| try |
| { |
| return new RegisterMessage(Collections.singletonList(Enum.valueOf(Event.Type.class, type))); |
| } |
| catch (IllegalArgumentException e) |
| { |
| System.err.println("[ERROR] Unknown event type: " + type); |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| private Map<String, String> readCredentials(Iterator<String> iter) |
| { |
| final Map<String, String> credentials = new HashMap<String, String>(); |
| while (iter.hasNext()) |
| { |
| String next = iter.next(); |
| String[] kv = next.split("="); |
| if (kv.length != 2) |
| { |
| System.err.println("[ERROR] Default authentication requires username & password"); |
| return null; |
| } |
| credentials.put(kv[0], kv[1]); |
| } |
| return credentials; |
| } |
| |
| private byte[] encodeCredentialsForSasl(Map<String, String> credentials) |
| { |
| byte[] username = credentials.get(PasswordAuthenticator.USERNAME_KEY).getBytes(StandardCharsets.UTF_8); |
| byte[] password = credentials.get(PasswordAuthenticator.PASSWORD_KEY).getBytes(StandardCharsets.UTF_8); |
| byte[] initialResponse = new byte[username.length + password.length + 2]; |
| initialResponse[0] = 0; |
| System.arraycopy(username, 0, initialResponse, 1, username.length); |
| initialResponse[username.length + 1] = 0; |
| System.arraycopy(password, 0, initialResponse, username.length + 2, password.length); |
| return initialResponse; |
| } |
| |
| public static void main(String[] args) throws Exception |
| { |
| DatabaseDescriptor.clientInitialization(); |
| |
| // Print usage if no argument is specified. |
| if (args.length < 2 || args.length > 3) |
| { |
| System.err.println("Usage: " + Client.class.getSimpleName() + " <host> <port> [<version>]"); |
| return; |
| } |
| |
| // Parse options. |
| String host = args[0]; |
| int port = Integer.parseInt(args[1]); |
| ProtocolVersion version = args.length == 3 ? ProtocolVersion.decode(Integer.parseInt(args[2]), ProtocolVersionLimit.SERVER_DEFAULT) : ProtocolVersion.CURRENT; |
| |
| ClientEncryptionOptions encryptionOptions = new ClientEncryptionOptions(); |
| System.out.println("CQL binary protocol console " + host + "@" + port + " using native protocol version " + version); |
| |
| new Client(host, port, version, encryptionOptions).run(); |
| System.exit(0); |
| } |
| } |