| /* |
| * 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.ignite.visor.commands |
| |
| import org.apache.ignite.IgniteSystemProperties._ |
| import org.apache.ignite.internal.IgniteVersionUtils._ |
| import org.apache.ignite.internal.util.scala.impl |
| import org.apache.ignite.internal.util.{IgniteUtils => U} |
| import org.apache.ignite.startup.cmdline.AboutDialog |
| import org.apache.ignite.visor.visor |
| import org.apache.ignite.visor.visor._ |
| |
| import jline.TerminalSupport |
| import jline.console.ConsoleReader |
| import jline.console.completer.Completer |
| import jline.internal.Configuration |
| |
| import javax.swing.ImageIcon |
| import java._ |
| import java.awt.Image |
| import java.io._ |
| import java.text.SimpleDateFormat |
| |
| import scala.io._ |
| |
| /** |
| * Command line Visor. |
| */ |
| class VisorConsole { |
| /** Version number. */ |
| protected def version() = VER_STR |
| |
| /** Copyright. */ |
| protected def copyright() = COPYRIGHT |
| |
| /** Program name. */ |
| protected val progName = sys.props.getOrElse(IGNITE_PROG_NAME, "ignitevisorcmd") |
| |
| /** |
| * Built-in commands. |
| */ |
| protected def addCommands() { |
| // Note the importing of implicit conversions. |
| org.apache.ignite.visor.commands.ack.VisorAckCommand |
| org.apache.ignite.visor.commands.alert.VisorAlertCommand |
| org.apache.ignite.visor.commands.cache.VisorCacheClearCommand |
| org.apache.ignite.visor.commands.cache.VisorCacheResetCommand |
| org.apache.ignite.visor.commands.cache.VisorCacheRebalanceCommand |
| org.apache.ignite.visor.commands.cache.VisorCacheCommand |
| org.apache.ignite.visor.commands.cache.VisorCacheModifyCommand |
| org.apache.ignite.visor.commands.config.VisorConfigurationCommand |
| org.apache.ignite.visor.commands.deploy.VisorDeployCommand |
| org.apache.ignite.visor.commands.disco.VisorDiscoveryCommand |
| org.apache.ignite.visor.commands.events.VisorEventsCommand |
| org.apache.ignite.visor.commands.gc.VisorGcCommand |
| org.apache.ignite.visor.commands.kill.VisorKillCommand |
| org.apache.ignite.visor.commands.node.VisorNodeCommand |
| org.apache.ignite.visor.commands.open.VisorOpenCommand |
| org.apache.ignite.visor.commands.ping.VisorPingCommand |
| org.apache.ignite.visor.commands.start.VisorStartCommand |
| org.apache.ignite.visor.commands.tasks.VisorTasksCommand |
| org.apache.ignite.visor.commands.top.VisorTopologyCommand |
| org.apache.ignite.visor.commands.vvm.VisorVvmCommand |
| } |
| |
| protected def parse(args: String) = { |
| val argLst = parseArgs(args) |
| |
| if (hasArgFlag("?", argLst) || hasArgFlag("help", argLst)) { |
| println("Usage:") |
| println(s" $progName [? | -help]|[{-v}{-np} {-cfg=<path>}]|[{-b=<path>} {-e=command1;command2;...} -quiet]") |
| println(" Where:") |
| println(" ?, /help, -help - show this message.") |
| println(" -v - verbose mode (quiet by default).") |
| println(" -np - no pause on exit (pause by default).") |
| println(" -cfg=<path> - connect with specified configuration.") |
| println(" -b=<path> - batch mode with file.") |
| println(" -e=cmd1;cmd2;... - batch mode with commands.") |
| println(" -nq - batch mode will not quit after execution (useful for alerts monitoring).") |
| println(" -quiet - batch mode will not print inform message and node log.") |
| |
| visor.quit() |
| } |
| |
| argLst |
| } |
| |
| protected def buildReader(argLst: ArgList): ConsoleReader = { |
| val cfgFile = argValue("cfg", argLst) |
| val batchFile = argValue("b", argLst) |
| val batchCommand = argValue("e", argLst) |
| val noBatchQuit = hasArgName("nq", argLst) |
| val quiet = hasArgName("quiet", argLst) |
| |
| if (noBatchQuit && batchFile.isEmpty && batchCommand.isEmpty) |
| visor.warn("Option \"-nq\" will be ignored because batch mode options \"-b\" or \"-e\" were not specified.") |
| |
| if (quiet && batchFile.isEmpty && batchCommand.isEmpty) |
| visor.warn("Option \"-quiet\" will be ignored because batch mode options \"-b\" or \"-e\" were not specified.") |
| |
| cfgFile.foreach(cfg => { |
| if (cfg.trim.isEmpty) { |
| visor.warn("Expected path to configuration after \"-cfg\" option.") |
| |
| visor.quit() |
| } |
| |
| if (batchFile.isDefined || batchCommand.isDefined) { |
| visor.warn("Options can't contains both -cfg and one of -b or -e options.") |
| |
| visor.quit() |
| } |
| |
| visor.searchCmd("open").foreach(_.withArgs("-cpath=" + cfg)) |
| }) |
| |
| if (batchFile.isDefined && batchCommand.isDefined) { |
| visor.warn("Options can't contains both command file and commands.") |
| |
| visor.quit() |
| } |
| |
| var batchStream: Option[String] = None |
| |
| batchFile.foreach(name => { |
| val f = U.resolveIgnitePath(name) |
| |
| if (f == null) { |
| visor.warn( |
| "Can't find batch commands file: " + name, |
| s"Usage: $progName {-b=<batch command file path>} {-e=command1;command2}" |
| ) |
| |
| visor.quit() |
| } |
| |
| batchStream = Some(Source.fromFile(f).getLines().mkString("\n")) |
| }) |
| |
| batchCommand.foreach(commands => batchStream = Some(commands.replaceAll(";", "\n"))) |
| |
| val inputStream = batchStream match { |
| case Some(cmd) => |
| visor.batchMode = true |
| visor.quiet = quiet |
| |
| val script = cmd + (if (cmd.last == '\n') "" else "\n") + (if (noBatchQuit) "" else "quit\n") |
| |
| new ByteArrayInputStream(script.getBytes("UTF-8")) |
| |
| case None => new FileInputStream(FileDescriptor.in) |
| } |
| |
| // Workaround for IDEA terminal. |
| val idea = Seq( |
| "com.intellij.rt.execution.application.AppMain", |
| "com.intellij.rt.execution.application.AppMainV2" |
| ).exists(cls => |
| try { |
| Class.forName(cls) |
| |
| true |
| } |
| catch { |
| case _: ClassNotFoundException => false |
| } |
| ) |
| |
| val term = if (idea) new TerminalSupport(false) {} else null |
| |
| val reader = new ConsoleReader(inputStream, System.out, term) |
| |
| reader.addCompleter(new VisorCommandCompleter(visor.commands)) |
| reader.addCompleter(new VisorFileNameCompleter()) |
| |
| reader |
| } |
| |
| protected def mainLoop(reader: ConsoleReader) { |
| if (!visor.quiet) |
| welcomeMessage() |
| |
| var ok = true |
| |
| // Wrap line symbol for user input. |
| val wrapLine = if (U.isWindows) "^" else "\\" |
| |
| val emptyArg = "^([a-zA-z!?]+)$".r |
| val varArg = "^([a-zA-z!?]+)\\s+(.+)$".r |
| |
| var line: String = null |
| |
| val buf = new StringBuilder |
| |
| while (ok) { |
| line = reader.readLine(if (visor.quiet) null else "visor> ") |
| |
| ok = line != null |
| |
| if (ok) { |
| line = line.trim |
| |
| if (line.endsWith(wrapLine)) { |
| buf.append(line.dropRight(1)) |
| } |
| else { |
| if (buf.nonEmpty) { |
| buf.append(line) |
| |
| line = buf.toString() |
| |
| buf.clear() |
| } |
| |
| try { |
| line match { |
| case emptyArg(c) => |
| visor.searchCmd(c) match { |
| case Some(cmdHolder) => cmdHolder.emptyArgs() |
| case _ => adviseToHelp(c) |
| } |
| case varArg(c, a) => |
| visor.searchCmd(c) match { |
| case Some(cmdHolder) => cmdHolder.withArgs(a.trim) |
| case _ => adviseToHelp(c) |
| } |
| case s if "".equals(s.trim) => // Ignore empty user input. |
| case _ => adviseToHelp(line) |
| } |
| } catch { |
| case ignore: Exception => |
| ignore.printStackTrace() |
| |
| adviseToHelp(line) |
| } |
| } |
| } |
| } |
| |
| if (visor.isConnected) { |
| visor.close() |
| } |
| } |
| |
| /** |
| * Prints standard 'Invalid command' error message. |
| */ |
| protected def adviseToHelp(input: String) { |
| visor.warn( |
| "Invalid command name: '" + input + "'", |
| "Type 'help' to print commands list." |
| ) |
| } |
| |
| /** |
| * Print banner, hint message on start. |
| */ |
| protected def welcomeMessage() { |
| println("___ _________________________ ________" + NL + |
| "__ | / /____ _/__ ___/__ __ \\___ __ \\" + NL + |
| "__ | / / __ / _____ \\ _ / / /__ /_/ /" + NL + |
| "__ |/ / __/ / ____/ / / /_/ / _ _, _/" + NL + |
| "_____/ /___/ /____/ \\____/ /_/ |_|" + NL + |
| NL + |
| "ADMIN CONSOLE" + NL + |
| copyright()) |
| |
| nl() |
| |
| status() |
| |
| println("\nType 'help' for more information.") |
| println("Type 'open' to join the grid.") |
| println("Type 'quit' to quit form Visor console.") |
| |
| nl() |
| } |
| |
| /** |
| * Setting up Mac OS specific system menu. |
| */ |
| protected def addAboutDialog() { |
| def urlIcon(iconName: String) = { |
| val iconPath = "org/apache/ignite/startup/cmdline/" + iconName |
| |
| val dockIconUrl = U.detectClassLoader(getClass).getResource(iconPath) |
| |
| assert(dockIconUrl != null, "Unknown icon path: " + iconPath) |
| |
| dockIconUrl |
| } |
| |
| try { |
| val appCls = Class.forName("com.apple.eawt.Application") |
| val aboutHndCls = Class.forName("com.apple.eawt.AboutHandler") |
| |
| val osxApp = appCls.getDeclaredMethod("getApplication").invoke(null) |
| |
| val dockIco = new ImageIcon(urlIcon("logo_ignite_128x128.png")) |
| |
| appCls.getDeclaredMethod("setDockIconImage", classOf[Image]).invoke(osxApp, dockIco.getImage) |
| |
| val bannerIconUrl = urlIcon("logo_ignite_48x48.png") |
| |
| val aboutHndProxy = java.lang.reflect.Proxy.newProxyInstance( |
| appCls.getClassLoader, |
| Array[Class[_]](aboutHndCls), |
| new java.lang.reflect.InvocationHandler { |
| def invoke(proxy: Any, mth: java.lang.reflect.Method, args: Array[Object]) = { |
| AboutDialog.centerShow("Visor - Ignite Shell Console", bannerIconUrl.toExternalForm, |
| version(), RELEASE_DATE, copyright()) |
| |
| null |
| } |
| }) |
| |
| appCls.getDeclaredMethod("setAboutHandler", aboutHndCls).invoke(osxApp, aboutHndProxy) |
| } |
| catch { |
| // Specifically ignore it here. |
| case _: Throwable => |
| } |
| } |
| } |
| |
| /** |
| * Command line Visor entry point. |
| */ |
| object VisorConsole extends VisorConsole with App { |
| addAboutDialog() |
| |
| addCommands() |
| |
| private val argLst = parse(args.mkString(" ")) |
| |
| private val reader = buildReader(argLst) |
| |
| visor.reader(reader) |
| |
| mainLoop(reader) |
| } |
| |
| /** |
| * Visor command list completer. |
| * |
| * @param commands Commands list. |
| */ |
| private[commands] class VisorCommandCompleter(commands: Seq[String]) extends Completer { |
| import scala.collection.JavaConversions._ |
| |
| /** ordered commands. */ |
| private final val strings = new util.TreeSet[String](commands) |
| |
| @impl def complete(buf: String, cursor: Int, candidates: util.List[CharSequence]): Int = { |
| // buffer could be null |
| assert(candidates != null) |
| |
| if (buf == null) |
| candidates.addAll(strings) |
| else |
| strings.tailSet(buf).takeWhile(_.startsWith(buf)).foreach(candidates.add) |
| |
| if (candidates.size == 1) |
| candidates.set(0, candidates.get(0) + " ") |
| |
| if (candidates.isEmpty) -1 else 0 |
| } |
| } |
| |
| /** |
| * File path completer for different place of path in command. |
| */ |
| private[commands] class VisorFileNameCompleter extends Completer { |
| protected lazy val getUserHome = Configuration.getUserHome |
| |
| protected lazy val separator = File.separator |
| |
| @impl def complete(buf: String, cursor: Int, candidates: util.List[CharSequence]): Int = { |
| assert(candidates != null) |
| |
| var ixBegin = 0 |
| |
| // extracted path from buffer. |
| val path = buf match { |
| case null => "" |
| case emptyStr if emptyStr.trim == "" => "" |
| case str => |
| // replace wrong '/' on windows. |
| val translated = if (U.isWindows) str.replace('/', '\\') else str |
| |
| // line before cursor. |
| val left = translated.substring(0, cursor) |
| |
| // path begin marker. |
| val quote = if (left.count(_ == '\"') % 2 == 1) "\"" |
| else if (left.count(_ == '\'') % 2 == 1) "\'" |
| else "" |
| |
| val splitterSz = quote.length + " ".length |
| |
| // path begin marker index. |
| ixBegin = left.lastIndexOf(" " + quote) |
| ixBegin = if (ixBegin != -1) ixBegin + splitterSz else left.length - 1 |
| |
| // path end marker index. |
| var ixEnd = translated.indexOf(quote + " ", cursor) |
| ixEnd = if (ixEnd != -1) ixEnd - splitterSz else translated.length |
| |
| // extract path. |
| translated.substring(ixBegin, ixEnd) |
| } |
| |
| // resolve path |
| val file = resolvePath(path) |
| |
| // file dir and part of file name for complete. |
| val (dir, partOfName) = if (file.isDirectory) (file, "") else (file.getParentFile, file.getName) |
| |
| // filter all files in directory by part of file name. |
| if (dir != null && dir.listFiles != null) { |
| val files = for (file <- dir.listFiles if file.getName.startsWith(partOfName)) yield file |
| |
| if (files.length == 1) { |
| val candidate = files(0) |
| |
| candidates.add(candidate.getName + (if (candidate.isDirectory) separator else " ")) |
| } |
| else |
| files.foreach(f => candidates.add(f.getName)) |
| } |
| |
| if (candidates.size > 0) ixBegin + path.lastIndexOf(separator) + separator.length else -1 |
| } |
| |
| /** |
| * Gets File representing the path passed in. First the check is made if path is in user home directory. |
| * If not, then the check is if path is absolute. |
| * If all checks fail, then related to the current dir File is returned. |
| * |
| * @param path - Path to resolve. |
| * @return Resolved path as File |
| */ |
| protected def resolvePath(path: String) = { |
| val homeDir = getUserHome |
| |
| val absFile = new File(path) |
| |
| // Special character: ~ maps to the user's home directory |
| if (path.startsWith("~" + separator)) |
| new File(homeDir.getPath, path.substring(1)) |
| else if (path.equals("~")) |
| homeDir.getParentFile |
| else if (absFile.exists() || absFile.getParentFile != null) // absolute path |
| absFile |
| else |
| new File(new File("").getAbsolutePath, path) |
| } |
| } |