Initial commit of mishell. Mishell is a scripting environment and console for remote jmx management.
See README.txt for details.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@441834 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/mishell/src/main/java/org/apache/felix/mishell/Commander.java b/mishell/src/main/java/org/apache/felix/mishell/Commander.java
new file mode 100644
index 0000000..5be7cb3
--- /dev/null
+++ b/mishell/src/main/java/org/apache/felix/mishell/Commander.java
@@ -0,0 +1,35 @@
+package org.apache.felix.mishell;
+
+import java.io.PrintStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+
+import javax.script.ScriptContext;
+
+
+public class Commander extends HashSet<Command> implements Command{
+ private Logger log=Logger.getLogger(this.getClass().getName());
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ String[] parsedCmd=cmd.split(" ");
+ if (parsedCmd.length==0)throw new CommandNotFoundException();
+ for (Command c: this) {
+ if (c.getName().equals(parsedCmd[0])){
+ log.finest("executing: "+c.getName());
+ c.executeCommand(cmd, out);
+ return;
+ }
+ }
+ throw new CommandNotFoundException();
+
+ }
+ public String getName() {
+ return "commander";
+ }
+ public Commander() {
+ }
+ public String getHelp() {
+ return "mishell commander";
+ }
+}
diff --git a/mishell/src/main/java/org/apache/felix/mishell/Console.java b/mishell/src/main/java/org/apache/felix/mishell/Console.java
new file mode 100644
index 0000000..01d9665
--- /dev/null
+++ b/mishell/src/main/java/org/apache/felix/mishell/Console.java
@@ -0,0 +1,220 @@
+package org.apache.felix.mishell;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.script.ScriptContext;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptException;
+
+import jline.ConsoleReader;
+import jline.ConsoleReaderInputStream;
+
+public class Console implements Runnable{
+ public static final String DEFAULT_LANGUAGE = "javascript";
+Logger log = Logger.getLogger(this.getClass().getCanonicalName());
+ Level l=Level.FINEST;
+ private String language=DEFAULT_LANGUAGE;
+ private String prompt;
+ BufferedReader in;
+ PrintStream out;
+ private Commander commander;
+ private boolean stop = false;
+ private JMXEngineContext engineContext;
+ private String scriptPath;
+
+ public Console(String language, String scriptPath) throws IOException{
+ if (language != null) this.language=language;
+ prompt="mishell."+language+"$ ";
+ /*
+ * Not used for the moment. It does not work inside Eclipse, and presents
+ problems from the command line
+ */
+ //useJline();
+ in=new BufferedReader(new InputStreamReader(System.in));
+ out=System.out;
+ commander= new Commander();
+ this.scriptPath=scriptPath;
+ addBuiltInCmds();
+ stop=false;
+ }
+ private void useJline() throws IOException{
+ ConsoleReader cr=new ConsoleReader();
+ ConsoleReaderInputStream.setIn(cr);
+
+ }
+ private void initLanguage() throws Exception{
+ initLanguage(null);
+ }
+ private void initLanguage(String name) throws Exception{
+ if(name!=null) {
+ engineContext = new JMXEngineContext(name);
+ }
+ else {
+ engineContext=new JMXEngineContext(language);
+ }
+ language=engineContext.getEngine().getFactory().getLanguageName();
+ prompt="mishell."+language+"$ ";
+ }
+ public void run() {
+ try {
+ initLanguage();
+ if (scriptPath==null)
+ runConsole();
+ else engineContext.getEngine().eval(new FileReader(scriptPath));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private void runConsole() throws Exception {
+ out.println("Welcome to Apache Mishell!!");
+ out.println("For getting help type 'help' ");
+ out.print(prompt);
+ while (!stop) {
+ try {
+ String cmd = in.readLine();
+ executeCommand(cmd);
+ out.print(prompt);
+ out.flush();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void stop() {
+ stop = true;
+ }
+
+ public void executeCommand(String cmd) {
+ try {
+ commander.executeCommand(cmd, out);
+ } catch (CommandNotFoundException e) {
+ try {
+ Object result=engineContext.getEngine().eval(cmd);
+ if(result==null)return;
+ else out.println(language+": "+result);
+ } catch (ScriptException se) {
+ out.println(se.getMessage());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ private void addBuiltInCmds(){
+ commander.add(new Command(){
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ Set<Entry<String, Object>> bindings= engineContext.getEngine().getContext().getBindings(ScriptContext.ENGINE_SCOPE).entrySet();
+ for (Entry<String, Object> entry : bindings) {
+ out.println(entry.getKey()+" ["+entry.getValue().getClass().getName()+"]\n");
+ }
+ }
+ public String getName() {
+ return "browse";
+ }
+ public String getHelp() {
+ return "prints the bindings for current engine";
+ }
+
+ });
+ commander.add(new Command(){
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ out.println("exiting console.");
+ stop=true;
+ }
+ public String getName() {
+ return "exit";
+ }
+ public String getHelp() {
+ return "exit this console";
+ }
+ });
+ commander.add(new Command(){
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ String[] args=cmd.split(" ");//TODO implement scape seqs
+ if(args.length>1)initLanguage(args[1]);
+ else for (ScriptEngineFactory factory: engineContext.getEngineManager().getEngineFactories()) {
+ out.print(factory.getLanguageName()+"; version "+factory.getLanguageVersion());
+ out.print("; AKA: ");
+ for(String alias: factory.getNames()) out.print(alias+" ");
+ out.print("\n");
+
+ }
+ }
+ public String getName() {
+ return "language";
+ }
+ public String getHelp() {
+ return "language [languageName]. Changes current language, or prints available ones";
+ }
+ });
+ commander.add(new Command(){
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ for (Command c : commander) {
+ out.println(c.getName()+": "+c.getHelp());
+ }
+ }
+ public String getName() {
+ return "help";
+ }
+ public String getHelp() {
+ return "Prints this help";
+ }
+ });
+ commander.add(new Command(){
+ public void executeCommand(String cmd, PrintStream out) throws Exception {
+ String[] args=cmd.split(" ");
+ if(args.length<2 || args.length>3) {
+ out.println(this.getHelp());
+ return;
+ }
+ Reader reader;
+ if (args.length==2) {
+ try {
+ reader=new FileReader(args[1]);
+ } catch(FileNotFoundException fne) {
+
+ out.println("Invalid path: "+args[1]);
+ return;
+ }
+ if(args.length==3 && (args[1].equals("-r")||args[1].equals("--remote"))) {
+ try {
+ URL url=new URL(args[2]);
+ reader=new InputStreamReader(url.openStream());
+ } catch (MalformedURLException mue) {
+ out.println("bad url");
+ return;
+ }
+ }
+ try {
+ engineContext.getEngine().eval(reader);
+ } catch (ScriptException se) {
+ out.println(se.getMessage());
+ }
+ }
+ }
+ public String getName() {
+ return "load";
+ }
+ public String getHelp() {
+ return "load [-r, --remote] scriptName. Loads the script. If remote, uses url instead of path";
+ }
+ });
+ }
+
+
+}
diff --git a/mishell/src/main/java/org/apache/felix/mishell/JMXEngineContext.java b/mishell/src/main/java/org/apache/felix/mishell/JMXEngineContext.java
new file mode 100644
index 0000000..462f7c4
--- /dev/null
+++ b/mishell/src/main/java/org/apache/felix/mishell/JMXEngineContext.java
@@ -0,0 +1,74 @@
+package org.apache.felix.mishell;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXServiceURL;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.jmxintrospector.MBeanProxyManager;
+
+//import org.apache.felix.jmood.core.CoreControllerMBean;
+//import org.apache.felix.jmood.core.FrameworkMBean;
+//import org.apache.felix.jmood.utils.ObjectNames;
+
+public class JMXEngineContext {
+ private ScriptEngine engine;
+ private ScriptEngineManager engineManager;
+
+
+ Logger log=Logger.getLogger(JMXEngineContext.class.getName());
+ Level l=Level.INFO;
+
+ public JMXEngineContext(String language)throws EngineNotFoundException{
+ engineManager=new ScriptEngineManager(this.getClass().getClassLoader());
+ log.log(l, "Available script engines are:");
+ for (ScriptEngineFactory sef : engineManager.getEngineFactories()) {
+ log.log(l, sef.getEngineName());
+ }
+ engine=engineManager.getEngineByName(language);
+ if (engine==null) throw new EngineNotFoundException(language);
+ JMoodProxyManager manager=new JMoodProxyManager();
+ String managerName=getVarName("manager");
+ engine.put(managerName, manager);
+ }
+ private String getVarName(String name){
+ if (engine.getFactory().getEngineName().equals("jruby")) name="$"+name;
+ return name;
+
+ }
+ public ScriptEngine getEngine() {
+ return engine;
+ }
+ public void setEngine(ScriptEngine engine) {
+ this.engine = engine;
+ }
+ public ScriptEngineManager getEngineManager() {
+ return engineManager;
+ }
+ public void setEngineManager(ScriptEngineManager engineManager) {
+ this.engineManager = engineManager;
+ }
+ }
+
+
+
+
+
diff --git a/mishell/src/main/java/org/apache/felix/mishell/Main.java b/mishell/src/main/java/org/apache/felix/mishell/Main.java
new file mode 100644
index 0000000..177278a
--- /dev/null
+++ b/mishell/src/main/java/org/apache/felix/mishell/Main.java
@@ -0,0 +1,24 @@
+package org.apache.felix.mishell;
+
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+public class Main {
+// private static final String log_props="target/classes/logging.properties";
+ private static final String log_props="logging.properties";
+
+ public static void main(String[] args) throws Exception{
+ Logger.getLogger(Console.class.getCanonicalName()).setLevel(Level.FINEST);
+ InputStream is=Main.class.getResourceAsStream(log_props);
+ LogManager.getLogManager().readConfiguration(is);
+ is.close();
+ String language=args.length==0?null:args[0];
+ String scriptPath=args.length<2?null:args[1];
+ Console m = new Console(language, scriptPath);
+ Thread t=new Thread(m);
+ t.setName("ConsoleThread");
+ t.start();
+ }
+}
diff --git a/mishell/src/main/java/org/apache/felix/mishell/logging.properties b/mishell/src/main/java/org/apache/felix/mishell/logging.properties
new file mode 100644
index 0000000..adcc69f
--- /dev/null
+++ b/mishell/src/main/java/org/apache/felix/mishell/logging.properties
@@ -0,0 +1,54 @@
+############################################################
+# Default Logging Configuration File
+#
+# You can use a different file by specifying a filename
+# with the java.util.logging.config.file system property.
+# For example java -Djava.util.logging.config.file=myfile
+############################################################
+
+############################################################
+# Global properties
+############################################################
+
+# "handlers" specifies a comma separated list of log Handler
+# classes. These handlers will be installed during VM startup.
+# Note that these classes must be on the system classpath.
+# By default we only configure a ConsoleHandler, which will only
+# show messages at the INFO and above levels.
+handlers= java.util.logging.ConsoleHandler
+
+# To also add the FileHandler, use the following line instead.
+#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+# Default global logging level.
+# This specifies which kinds of events are logged across
+# all loggers. For any given facility this global level
+# can be overriden by a facility specific level
+# Note that the ConsoleHandler also has a separate level
+# setting to limit messages printed to the console.
+.level= FINE
+
+############################################################
+# Handler specific properties.
+# Describes specific configuration info for Handlers.
+############################################################
+
+# default file output is in user's home directory.
+java.util.logging.FileHandler.pattern = %h/java%u.log
+java.util.logging.FileHandler.limit = 50000
+java.util.logging.FileHandler.count = 1
+java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
+
+# Limit the message that are printed on the console to INFO and above.
+java.util.logging.ConsoleHandler.level = INFO
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+
+
+############################################################
+# Facility specific properties.
+# Provides extra control for each logger.
+############################################################
+
+# For example, set the com.xyz.foo logger to only log SEVERE
+# messages:
+com.xyz.foo.level = SEVERE
diff --git a/mishell/src/main/resources/manifest.mf b/mishell/src/main/resources/manifest.mf
new file mode 100644
index 0000000..a2da53e
--- /dev/null
+++ b/mishell/src/main/resources/manifest.mf
@@ -0,0 +1 @@
+Main-Class: org.apache.felix.mishell.Main
\ No newline at end of file
diff --git a/mishell/src/main/resources/scripts/hello.js b/mishell/src/main/resources/scripts/hello.js
new file mode 100644
index 0000000..baef51f
--- /dev/null
+++ b/mishell/src/main/resources/scripts/hello.js
@@ -0,0 +1,6 @@
+/*
+ * This is sample JavaScript file that can be loaded into script console.
+ * This file prints "hello, world".
+ */
+
+echo("hello, world");
diff --git a/mishell/src/main/resources/scripts/invoke.js b/mishell/src/main/resources/scripts/invoke.js
new file mode 100644
index 0000000..20823a1
--- /dev/null
+++ b/mishell/src/main/resources/scripts/invoke.js
@@ -0,0 +1,18 @@
+/*
+ * This script demonstrates "invokeMBean" function. Instead
+ * of using MXBean proxy or script wrapper object returned by
+ * 'mbean' function, this file uses direct invoke on MBean.
+ *
+ * To use this particular script, load this script file in
+ * script console prompt and call resetPeakThreadCount().
+
+ */
+
+/**
+ * Resets the peak thread count to the current number of live threads.
+ *
+ */
+function resetPeakThreadCount() {
+ return invokeMBean("java.lang:type=Threading", "resetPeakThreadCount", [], "");
+}
+
diff --git a/mishell/src/main/resources/scripts/jtop.js b/mishell/src/main/resources/scripts/jtop.js
new file mode 100644
index 0000000..23e7531
--- /dev/null
+++ b/mishell/src/main/resources/scripts/jtop.js
@@ -0,0 +1,73 @@
+/*
+ * This code is "ported" from JTop demo. This file defines
+ * 'jtop' function. jtop prints threads sorting by CPU time.
+ * jtop can be called once or periodically from a timer thread.
+ * To call this once, just call 'jtop()' in script console prompt.
+ * To call jtop in a timer thread, you can use
+ *
+ * var t = setTimeout(function () { jtop(print); }, 2000);
+ *
+ * The above call prints threads in sorted order for every 2 seconds.
+ * The print output goes to OS console window from which jconsole was
+ * started. The timer can be cancelled later by clearTimeout() function
+ * as shown below:
+ *
+ * clearTimeout(t);
+ */
+
+/**
+ * This function returns a List of Map.Entry objects
+ * in which each entry maps cpu time to ThreadInfo.
+ */
+function getThreadList() {
+ var tmbean = newPlatformMXBeanProxy(
+ "java.lang:type=Threading",
+ java.lang.management.ThreadMXBean);
+
+ if (!tmbean.isThreadCpuTimeSupported()) {
+ return;
+ }
+
+ tmbean.setThreadCpuTimeEnabled(true);
+
+ var tids = tmbean.allThreadIds;
+ var tinfos = tmbean["getThreadInfo(long[])"](tids);
+
+ var map = new java.util.TreeMap();
+ for (var i in tids) {
+ var cpuTime = tmbean.getThreadCpuTime(tids[i]);
+ if (cpuTime != -1 && tinfos[i] != null) {
+ map.put(cpuTime, tinfos[i]);
+ }
+ }
+ var list = new java.util.ArrayList(map.entrySet());
+ java.util.Collections.reverse(list);
+ return list;
+}
+
+/**
+ * This function prints threads sorted by CPU time.
+ *
+ * @param printFunc function called back to print [optional]
+ *
+ * By default, it uses 'echo' function to print in screen.
+ * Other choices could be 'print' (prints in console), 'alert'
+ * to show message box. Or you can define a function that writes
+ * the output to a text file.
+ */
+function jtop(printFunc) {
+ if (printFunc == undefined) {
+ printFunc = echo;
+ }
+ var list = getThreadList();
+ var itr = list.iterator();
+ printFunc("time - state - name");
+ while (itr.hasNext()) {
+ var entry = itr.next();
+ // time is in nanoseconds - convert to seconds
+ var time = entry.key / 1.0e9;
+ var name = entry.value.threadName;
+ var state = entry.value.threadState;
+ printFunc(time + " - " + state + " - " + name);
+ }
+}
diff --git a/mishell/src/main/resources/scripts/verbose.js b/mishell/src/main/resources/scripts/verbose.js
new file mode 100644
index 0000000..dac17f8
--- /dev/null
+++ b/mishell/src/main/resources/scripts/verbose.js
@@ -0,0 +1,49 @@
+/*
+ * This script demonstrates "getMBeanAttribute"
+ * and "setMBeanAttribute" functions. Instead of using
+ * MXBean proxy or script wrapper object returned by
+ * 'mbean' function, this file uses direct get/set MBean
+ * attribute functions.
+ *
+ * To use this particular script, load this script file in
+ * script console prompt and call verboseGC or verboseClass
+ * functions. These functions based on events such as
+ * heap threshold crossing a given limit. i.e., A timer thread
+ * can keep checking for threshold event and then turn on
+ * verbose:gc or verbose:class based on expected event.
+
+ */
+
+/**
+ * Get or set verbose GC flag.
+ *
+ * @param flag verbose mode flag [optional]
+ *
+ * If flag is not passed verboseGC returns current
+ * flag value.
+ */
+function verboseGC(flag) {
+ if (flag == undefined) {
+ // no argument passed. interpret this as 'get'
+ return getMBeanAttribute("java.lang:type=Memory", "Verbose");
+ } else {
+ return setMBeanAttribute("java.lang:type=Memory", "Verbose", flag);
+ }
+}
+
+/**
+ * Get or set verbose class flag.
+ *
+ * @param flag verbose mode flag [optional]
+ *
+ * If flag is not passed verboseClass returns current
+ * flag value.
+ */
+function verboseClass(flag) {
+ if (flag == undefined) {
+ // no argument passed. interpret this as 'get'
+ return getMBeanAttribute("java.lang:type=ClassLoading", "Verbose");
+ } else {
+ return setMBeanAttribute("java.lang:type=ClassLoading", "Verbose", flag);
+ }
+}