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.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.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 ); } } 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.newInstance( 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 ); } }