CommandCenter.java

package org.cyclopsgroup.jmxterm.cc;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.JMException;
import javax.management.remote.JMXServiceURL;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.cyclopsgroup.caff.token.EscapingValueTokenizer;
import org.cyclopsgroup.caff.token.TokenEvent;
import org.cyclopsgroup.caff.token.TokenEventHandler;
import org.cyclopsgroup.caff.token.ValueTokenizer;
import org.cyclopsgroup.jcli.ArgumentProcessor;
import org.cyclopsgroup.jmxterm.Command;
import org.cyclopsgroup.jmxterm.CommandFactory;
import org.cyclopsgroup.jmxterm.JavaProcessManager;
import org.cyclopsgroup.jmxterm.Session;
import org.cyclopsgroup.jmxterm.io.CommandInput;
import org.cyclopsgroup.jmxterm.io.CommandOutput;
import org.cyclopsgroup.jmxterm.io.RuntimeIOException;
import org.cyclopsgroup.jmxterm.io.VerboseLevel;

/**
 * Facade class where commands are maintained and executed
 *
 * @author <a href="mailto:jiaqi.guo@gmail.com">Jiaqi Guo</a>
 */
public class CommandCenter {
  private static final String COMMAND_DELIMITER = "&&";
  static final String ESCAPE_CHAR_REGEX = "(?<!\\\\)#";

  /** Argument tokenizer that parses arguments */
  final ValueTokenizer argTokenizer = new EscapingValueTokenizer();

  /** Command factory that creates commands */
  final CommandFactory commandFactory;

  private final Lock lock = new ReentrantLock();

  private final JavaProcessManager processManager;

  /** A handler to session */
  final Session session;

  /**
   * Constructor with given output {@link PrintWriter}
   *
   * @param output Message output. It can't be NULL
   * @param input Command line input
   * @throws IOException Thrown for file access failure
   */
  public CommandCenter(CommandOutput output, CommandInput input) throws IOException {
    this(output, input, new PredefinedCommandFactory());
  }

  /**
   * This constructor is for testing purpose only
   *
   * @param output Output result
   * @param input Command input
   * @param commandFactory Given command factory
   * @throws IOException IO problem
   */
  public CommandCenter(CommandOutput output, CommandInput input, CommandFactory commandFactory)
      throws IOException {
    Validate.notNull(output, "Output can't be NULL");
    Validate.notNull(commandFactory, "Command factory can't be NULL");
    processManager = new JPMFactory().getProcessManager();
    this.session = new SessionImpl(output, input, processManager);
    this.commandFactory = commandFactory;
  }

  /** Close session */
  public void close() {
    session.close();
  }

  /**
   * @param url MBeanServer location. It can be <code>AAA:###</code> or full JMX server URL
   * @param env Environment variables
   * @throws IOException Thrown when connection can't be established
   */
  public void connect(JMXServiceURL url, Map<String, Object> env) throws IOException {
    Validate.notNull(url, "URL can't be NULL");
    session.connect(url, env);
  }

  private void doExecute(String command) throws JMException {
    command = StringUtils.trimToNull(command);
    // Ignore empty line
    if (command == null) {
      return;
    }
    // Ignore line comment
    if (command.startsWith("#")) {
      return;
    }
    // Truncate command if there's # character
    // Note: this allows people to set properties to values with # (e.g.: set AttributeA
    // /a/\\#something)
    command =
        command.split(ESCAPE_CHAR_REGEX)[0] // take out all commented out sections
            .replace("\\#", "#"); // fix escaped to non-escaped comment charaters
    // If command includes multiple segments, call them one by one using recursive call
    if (command.indexOf(COMMAND_DELIMITER) != -1) {
      String[] commands = StringUtils.split(command, COMMAND_DELIMITER);
      for (String c : commands) {
        execute(c);
      }
      return;
    }

    // Take the first argument out since it's command name
    final List<String> args = new ArrayList<String>();
    argTokenizer.parse(
        command,
        new TokenEventHandler() {
          public void handleEvent(TokenEvent event) {
            args.add(event.getToken());
          }
        });
    String commandName = args.remove(0);
    // Leave the rest of arguments for command
    String[] commandArgs = args.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
    // Call command with parsed command name and arguments
    try {
      doExecute(commandName, commandArgs, command);
    } catch (IOException e) {
      throw new RuntimeIOException("Runtime IO exception: " + e.getMessage(), e);
    }
  }

  // TODO: The casting can be removed with the next release of jcli.
  @SuppressWarnings("unchecked")
  private void doExecute(String commandName, String[] commandArgs, String originalCommand)
      throws JMException, IOException {
    Command cmd = commandFactory.createCommand(commandName);
    if (cmd instanceof HelpCommand) {
      ((HelpCommand) cmd).setCommandCenter(this);
    }
    ArgumentProcessor<Command> ap =
        (ArgumentProcessor<Command>) ArgumentProcessor.forType(cmd.getClass());

    ap.process(commandArgs, cmd);
    // Print out usage if help option is specified
    if (cmd.isHelp()) {
      ap.printHelp(new PrintWriter(System.out, true));
      return;
    }
    cmd.setSession(session);
    // Make sure concurrency and run command
    lock.lock();
    try {
      cmd.execute();
    } finally {
      lock.unlock();
    }
  }

  /**
   * Execute a command. Command can be a valid full command, a comment, command followed by comment
   * or empty
   *
   * @param command String command to execute
   * @return True if successful
   */
  public boolean execute(String command) {
    try {
      doExecute(command);
      return true;
    } catch (JMException e) {
      session.output.printError(e);
      return false;
    } catch (RuntimeException e) {
      session.output.printError(e);
      return false;
    }
  }

  /**
   * @return Set of command names
   */
  public Set<String> getCommandNames() {
    return commandFactory.getCommandTypes().keySet();
  }

  /**
   * @param name Command name
   * @return Type of command associated with given name
   */
  public Class<? extends Command> getCommandType(String name) {
    return commandFactory.getCommandTypes().get(name);
  }

  /**
   * @return Java process manager implementation
   */
  public final JavaProcessManager getProcessManager() {
    return processManager;
  }

  /**
   * @return True if command center is closed
   */
  public boolean isClosed() {
    return session.isClosed();
  }

  /**
   * @param verboseLevel New verbose level value
   */
  public void setVerboseLevel(VerboseLevel verboseLevel) {
    session.setVerboseLevel(verboseLevel);
  }
}