View Javadoc
1   package org.cyclopsgroup.jmxterm.cc;
2   
3   import java.io.IOException;
4   import java.io.PrintWriter;
5   import java.util.ArrayList;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Set;
9   import java.util.concurrent.locks.Lock;
10  import java.util.concurrent.locks.ReentrantLock;
11  
12  import javax.management.JMException;
13  import javax.management.remote.JMXServiceURL;
14  
15  import org.apache.commons.lang3.ArrayUtils;
16  import org.apache.commons.lang3.StringUtils;
17  import org.apache.commons.lang3.Validate;
18  import org.cyclopsgroup.caff.token.EscapingValueTokenizer;
19  import org.cyclopsgroup.caff.token.TokenEvent;
20  import org.cyclopsgroup.caff.token.TokenEventHandler;
21  import org.cyclopsgroup.caff.token.ValueTokenizer;
22  import org.cyclopsgroup.jcli.ArgumentProcessor;
23  import org.cyclopsgroup.jmxterm.Command;
24  import org.cyclopsgroup.jmxterm.CommandFactory;
25  import org.cyclopsgroup.jmxterm.JavaProcessManager;
26  import org.cyclopsgroup.jmxterm.Session;
27  import org.cyclopsgroup.jmxterm.io.CommandInput;
28  import org.cyclopsgroup.jmxterm.io.CommandOutput;
29  import org.cyclopsgroup.jmxterm.io.RuntimeIOException;
30  import org.cyclopsgroup.jmxterm.io.VerboseLevel;
31  
32  /**
33   * Facade class where commands are maintained and executed
34   *
35   * @author <a href="mailto:jiaqi.guo@gmail.com">Jiaqi Guo</a>
36   */
37  public class CommandCenter {
38    private static final String COMMAND_DELIMITER = "&&";
39    static final String ESCAPE_CHAR_REGEX = "(?<!\\\\)#";
40  
41    /**
42     * Argument tokenizer that parses arguments
43     */
44    final ValueTokenizer argTokenizer = new EscapingValueTokenizer();
45  
46    /**
47     * Command factory that creates commands
48     */
49    final CommandFactory commandFactory;
50  
51    private final Lock lock = new ReentrantLock();
52  
53    private final JavaProcessManager processManager;
54  
55    /**
56     * A handler to session
57     */
58    final Session session;
59  
60    /**
61     * Constructor with given output {@link PrintWriter}
62     *
63     * @param output Message output. It can't be NULL
64     * @param input Command line input
65     * @throws IOException Thrown for file access failure
66     */
67    public CommandCenter(CommandOutput output, CommandInput input) throws IOException {
68      this(output, input, new PredefinedCommandFactory());
69    }
70  
71    /**
72     * This constructor is for testing purpose only
73     *
74     * @param output Output result
75     * @param input Command input
76     * @param commandFactory Given command factory
77     * @throws IOException IO problem
78     */
79    public CommandCenter(CommandOutput output, CommandInput input, CommandFactory commandFactory)
80        throws IOException {
81      Validate.notNull(output, "Output can't be NULL");
82      Validate.notNull(commandFactory, "Command factory can't be NULL");
83      processManager = new JPMFactory().getProcessManager();
84      this.session = new SessionImpl(output, input, processManager);
85      this.commandFactory = commandFactory;
86  
87    }
88  
89    /**
90     * Close session
91     */
92    public void close() {
93      session.close();
94    }
95  
96    /**
97     * @param url MBeanServer location. It can be <code>AAA:###</code> or full JMX server URL
98     * @param env Environment variables
99     * @throws IOException Thrown when connection can't be established
100    */
101   public void connect(JMXServiceURL url, Map<String, Object> env) throws IOException {
102     Validate.notNull(url, "URL can't be NULL");
103     session.connect(url, env);
104   }
105 
106   private void doExecute(String command) throws JMException {
107     command = StringUtils.trimToNull(command);
108     // Ignore empty line
109     if (command == null) {
110       return;
111     }
112     // Ignore line comment
113     if (command.startsWith("#")) {
114       return;
115     }
116     // Truncate command if there's # character
117     // Note: this allows people to set properties to values with # (e.g.: set AttributeA
118     // /a/\\#something)
119     command = command.split(ESCAPE_CHAR_REGEX)[0] // take out all commented out sections
120         .replace("\\#", "#"); // fix escaped to non-escaped comment charaters
121     // If command includes multiple segments, call them one by one using recursive call
122     if (command.indexOf(COMMAND_DELIMITER) != -1) {
123       String[] commands = StringUtils.split(command, COMMAND_DELIMITER);
124       for (String c : commands) {
125         execute(c);
126       }
127       return;
128     }
129 
130     // Take the first argument out since it's command name
131     final List<String> args = new ArrayList<String>();
132     argTokenizer.parse(command, new TokenEventHandler() {
133       public void handleEvent(TokenEvent event) {
134         args.add(event.getToken());
135       }
136     });
137     String commandName = args.remove(0);
138     // Leave the rest of arguments for command
139     String[] commandArgs = args.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
140     // Call command with parsed command name and arguments
141     try {
142       doExecute(commandName, commandArgs, command);
143     } catch (IOException e) {
144       throw new RuntimeIOException("Runtime IO exception: " + e.getMessage(), e);
145     }
146   }
147 
148   // TODO: The casting can be removed with the next release of jcli.
149   @SuppressWarnings("unchecked")
150   private void doExecute(String commandName, String[] commandArgs, String originalCommand)
151       throws JMException, IOException {
152     Command cmd = commandFactory.createCommand(commandName);
153     if (cmd instanceof HelpCommand) {
154       ((HelpCommand) cmd).setCommandCenter(this);
155     }
156     ArgumentProcessor<Command> ap =
157         (ArgumentProcessor<Command>) ArgumentProcessor.forType(cmd.getClass());
158 
159     ap.process(commandArgs, cmd);
160     // Print out usage if help option is specified
161     if (cmd.isHelp()) {
162       ap.printHelp(new PrintWriter(System.out, true));
163       return;
164     }
165     cmd.setSession(session);
166     // Make sure concurrency and run command
167     lock.lock();
168     try {
169       cmd.execute();
170     } finally {
171       lock.unlock();
172     }
173   }
174 
175   /**
176    * Execute a command. Command can be a valid full command, a comment, command followed by comment
177    * or empty
178    *
179    * @param command String command to execute
180    * @return True if successful
181    */
182   public boolean execute(String command) {
183     try {
184       doExecute(command);
185       return true;
186     } catch (JMException e) {
187       session.output.printError(e);
188       return false;
189     } catch (RuntimeException e) {
190       session.output.printError(e);
191       return false;
192     }
193   }
194 
195   /**
196    * @return Set of command names
197    */
198   public Set<String> getCommandNames() {
199     return commandFactory.getCommandTypes().keySet();
200   }
201 
202   /**
203    * @param name Command name
204    * @return Type of command associated with given name
205    */
206   public Class<? extends Command> getCommandType(String name) {
207     return commandFactory.getCommandTypes().get(name);
208   }
209 
210   /**
211    * @return Java process manager implementation
212    */
213   public final JavaProcessManager getProcessManager() {
214     return processManager;
215   }
216 
217   /**
218    * @return True if command center is closed
219    */
220   public boolean isClosed() {
221     return session.isClosed();
222   }
223 
224   /**
225    * @param verboseLevel New verbose level value
226    */
227   public void setVerboseLevel(VerboseLevel verboseLevel) {
228     session.setVerboseLevel(verboseLevel);
229   }
230 }