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);
+    }
+}