View Javadoc
1   package org.cyclopsgroup.jmxterm.cmd;
2   
3   import org.apache.commons.lang3.Validate;
4   import org.cyclopsgroup.jcli.annotation.Argument;
5   import org.cyclopsgroup.jcli.annotation.Cli;
6   import org.cyclopsgroup.jcli.annotation.MultiValue;
7   import org.cyclopsgroup.jcli.annotation.Option;
8   import org.cyclopsgroup.jmxterm.Command;
9   import org.cyclopsgroup.jmxterm.Session;
10  import org.cyclopsgroup.jmxterm.io.CommandOutput;
11  import org.cyclopsgroup.jmxterm.io.JlineCommandInput;
12  import org.jline.reader.impl.LineReaderImpl;
13  
14  import javax.management.JMException;
15  import javax.management.MBeanAttributeInfo;
16  import javax.management.MBeanServerConnection;
17  import javax.management.ObjectName;
18  import java.io.IOException;
19  import java.text.FieldPosition;
20  import java.text.MessageFormat;
21  import java.util.ArrayList;
22  import java.util.Date;
23  import java.util.List;
24  import java.util.concurrent.Executors;
25  import java.util.concurrent.ScheduledExecutorService;
26  import java.util.concurrent.TimeUnit;
27  
28  /**
29   * Command to watch an MBean attribute TODO Consider the use case for CSV file backend generation
30   *
31   * @author <a href="mailto:jiaqi.guo@gmail.com">Jiaqi Guo</a>
32   */
33  @Cli(name = "watch", description = "Watch the value of one MBean attribute constantly",
34      note = "DO NOT call this command in a script and expect decent output")
35  public class WatchCommand extends Command {
36    private static class ConsoleOutput extends Output {
37      private final LineReaderImpl console;
38  
39      private ConsoleOutput(Session session) {
40        if (!(session.getInput() instanceof JlineCommandInput)) {
41          throw new IllegalStateException("Under current context, watch command can't execute.");
42        }
43        this.console = ((JlineCommandInput) session.getInput()).getConsole();
44      }
45  
46      void printLine(String line) throws IOException {
47        console.redrawLine();
48        console.getTerminal().writer().print(line);
49        console.flush();
50      }
51    }
52  
53    private static abstract class Output {
54      abstract void printLine(String line) throws IOException;
55    }
56  
57    private static class ReportOutput extends Output {
58      private final CommandOutput out;
59  
60      private ReportOutput(Session session) {
61        this.out = session.output;
62      }
63  
64      @Override
65      void printLine(String line) {
66        out.println(line);
67      }
68  
69    }
70  
71    private static final String BUILDING_ATTRIBUTE_NOW = "%now";
72  
73    private static final int DEFAULT_REFRESH_INTERVAL = 1;
74  
75    private List<String> attributes = new ArrayList<String>();
76  
77    private String outputFormat;
78  
79    private int refreshInterval = DEFAULT_REFRESH_INTERVAL;
80  
81    private boolean report;
82  
83    private int stopAfter;
84  
85    @Override
86    public List<String> doSuggestArgument() throws IOException, JMException {
87      if (getSession().getBean() != null) {
88        MBeanServerConnection con = getSession().getConnection().getServerConnection();
89        MBeanAttributeInfo[] ais =
90            con.getMBeanInfo(new ObjectName(getSession().getBean())).getAttributes();
91        List<String> results = new ArrayList<String>(ais.length);
92        for (MBeanAttributeInfo ai : ais) {
93          results.add(ai.getName());
94        }
95        results.add(BUILDING_ATTRIBUTE_NOW);
96        return results;
97      }
98      return null;
99    }
100 
101   @Override
102   public void execute() throws IOException, JMException {
103     if (report && stopAfter == 0) {
104       throw new IllegalArgumentException(
105           "When --report is sepcified, --stopafter(-s) must be specificed");
106     }
107     Session session = getSession();
108     String domain = DomainCommand.getDomainName(null, session);
109     if (domain == null) {
110       throw new IllegalStateException("Please specify a domain using domain command first.");
111     }
112     String beanName = BeanCommand.getBeanName(null, domain, session);
113     if (beanName == null) {
114       throw new IllegalStateException("Please specify a bean using bean command first.");
115     }
116 
117     final ObjectName name = new ObjectName(beanName);
118     final MBeanServerConnection con = session.getConnection().getServerConnection();
119     final Output output;
120     if (report) {
121       output = new ReportOutput(session);
122     } else {
123       output = new ConsoleOutput(session);
124       getSession().output.printMessage("press any key to stop. DO NOT press Ctrl+C !!!");
125     }
126 
127     final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
128     executor.scheduleWithFixedDelay(new Runnable() {
129       public void run() {
130         try {
131           printValues(name, con, output);
132         } catch (IOException e) {
133           getSession().output.printError(e);
134         }
135       }
136     }, 0, refreshInterval, TimeUnit.SECONDS);
137     if (stopAfter > 0) {
138       executor.schedule(new Runnable() {
139         public void run() {
140           executor.shutdownNow();
141         }
142       }, stopAfter, TimeUnit.SECONDS);
143     }
144     if (!report) {
145       System.in.read();
146       System.out.println();
147       executor.shutdownNow();
148     }
149 
150     session.output.println("");
151   }
152 
153   private Object getAttributeValue(ObjectName beanName, String attributeName,
154       MBeanServerConnection connection) throws IOException {
155     // $now is a reserved keyword for current java.util.Date
156     if (attributeName.equals(BUILDING_ATTRIBUTE_NOW)) {
157       return new Date();
158     }
159     try {
160       return connection.getAttribute(beanName, attributeName);
161     } catch (JMException e) {
162       return e.getClass().getSimpleName();
163     }
164   }
165 
166   private void printValues(ObjectName beanName, MBeanServerConnection connection, Output output)
167       throws IOException {
168     StringBuffer result = new StringBuffer();
169     if (outputFormat == null) {
170       boolean first = true;
171       for (String attributeName : attributes) {
172         if (first) {
173           first = false;
174         } else {
175           result.append(", ");
176         }
177         result.append(getAttributeValue(beanName, attributeName, connection));
178       }
179     } else {
180       Object[] values = new Object[attributes.size()];
181       int i = 0;
182       for (String attributeNamne : attributes) {
183         values[i++] = getAttributeValue(beanName, attributeNamne, connection);
184       }
185       MessageFormat format = new MessageFormat(outputFormat);
186       format.format(values, result, new FieldPosition(0));
187     }
188     output.printLine(result.toString());
189   }
190 
191   /**
192    * @param attributes Name of attributes to watch
193    */
194   @MultiValue(listType = ArrayList.class, minValues = 1)
195   @Argument(displayName = "attr", description = "Name of attributes to watch")
196   public final void setAttributes(List<String> attributes) {
197     this.attributes = attributes;
198   }
199 
200   /**
201    * @param outputFormat Pattern used in {@link MessageFormat}
202    */
203   @Option(name = "f", longName = "format", displayName = "expr",
204       description = "Java pattern(java.text.MessageFormat) to print attribute values")
205   public final void setOutputFormat(String outputFormat) {
206     this.outputFormat = outputFormat;
207   }
208 
209   /**
210    * @param refreshInterval Refreshing interval in seconds
211    */
212   @Option(name = "i", longName = "interval", displayName = "sec",
213       description = "Optional number of seconds between consecutive poll, default is 1 second",
214       defaultValue = "1")
215   public final void setRefreshInterval(int refreshInterval) {
216     Validate.isTrue(refreshInterval > 0, "Invalid interval value " + refreshInterval);
217     this.refreshInterval = refreshInterval;
218   }
219 
220   /**
221    * @param report True to output result line by line as report
222    */
223   @Option(name = "r", longName = "report", description = "Output result line by line as report")
224   public final void setReport(boolean report) {
225     this.report = report;
226   }
227 
228   /**
229    * @param stopAfter After this number of seconds, stop watching
230    */
231   @Option(name = "s", longName = "stopafter", displayName = "sec",
232       description = "Stop after watching a number of seconds")
233   public final void setStopAfter(int stopAfter) {
234     Validate.isTrue(stopAfter >= 0, "Invalid stop after argument " + stopAfter);
235     this.stopAfter = stopAfter;
236   }
237 }