FELIX-563 Apply patch supplied by Fredrik Kjellberg (Thanks)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@683680 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java
new file mode 100644
index 0000000..06fcc52
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ShellServlet.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.webconsole.internal.misc;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.shell.ShellService;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+public class ShellServlet extends AbstractWebConsolePlugin implements OsgiManagerPlugin
+{
+    private ServiceTracker shellTracker;
+
+
+    public String getLabel()
+    {
+        return "shell";
+    }
+
+
+    public String getTitle()
+    {
+        return "Shell";
+    }
+
+
+    protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+        response.setCharacterEncoding( "utf-8" );
+        response.setContentType( "text/html" );
+
+        PrintWriter pw = response.getWriter();
+
+        try
+        {
+            String command = request.getParameter( "command" );
+
+            pw.print( "<span class=\"consolecommand\">-&gt; " );
+            pw.print( command == null ? "" : escapeHtml( command ) );
+            pw.println( "</span><br />" );
+
+            if ( command != null && !"".equals( command ) )
+            {
+                ShellService shellService = getShellService();
+                if ( shellService != null )
+                {
+                    ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+                    ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+
+                    shellService.executeCommand( command, new PrintStream( baosOut, true ), new PrintStream( baosErr,
+                        true ) );
+                    if ( baosOut.size() > 0 )
+                    {
+                        pw.print( escapeHtml( new String( baosOut.toByteArray() ) ) );
+                    }
+                    if ( baosErr.size() > 0 )
+                    {
+                        pw.print( "<span class=\"error\">" );
+                        pw.print( escapeHtml( new String( baosErr.toByteArray() ) ) );
+                        pw.println( "</span>" );
+                    }
+                }
+                else
+                {
+                    pw.print( "<span class=\"error\">" );
+                    pw.print( "Error: No shell service available<br />" );
+                    pw.println( "</span>" );
+                }
+            }
+        }
+        catch ( Throwable t )
+        {
+            pw.print( "<span class=\"error\">" );
+            StringWriter out = new StringWriter();
+            t.printStackTrace( new PrintWriter( out, true ) );
+            pw.print( escapeHtml( out.toString() ) );
+            pw.println( "</span>" );
+        }
+    }
+
+
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+        IOException
+    {
+        PrintWriter pw = response.getWriter();
+
+        String appRoot = request.getContextPath() + request.getServletPath();
+        pw.println( "<link href=\"" + appRoot + "/res/ui/shell.css\" rel=\"stylesheet\" type=\"text/css\" />" );
+        pw.println( "<script src=\"" + appRoot + "/res/ui/shell.js\" type=\"text/javascript\"></script>" );
+
+        pw.println( "<br />" );
+
+        pw.println( "<form name=\"shellCommandForm\" method=\"post\" action=\"" + appRoot
+            + "/shell\" title=\"Shell Command\" onsubmit=\"runShellCommand();return false;\">" );
+
+        pw.println( "<div class=\"consolebuttons\">" );
+        pw.println( "<input class=\"submit\" type=\"button\" value=\"Help\" onclick=\"executeCommand('help');\"/>" );
+        pw
+            .println( "&nbsp;&nbsp;<input class=\"submit\" type=\"button\" value=\"Clear\" onclick=\"clearConsole();\"/>" );
+        pw.println( "</div>" );
+
+        pw.println( "<div id=\"consoleframe\" class=\"consoleframe\" onclick=\"shellCommandFocus();\">" );
+        pw.println( "<div id=\"console\" class=\"console\" onclick=\"shellCommandFocus();\">" );
+        pw.println( "</div>" );
+
+        pw.println( "<span class=\"prompt\">" );
+        pw.println( "-&gt; <input type=\"text\" name=\"command\" value=\"\" class=\"command\" autocomplete=\"off\"/>" );
+        pw.println( "</span>" );
+
+        pw.println( "</div>" );
+
+        pw.println( "</form>" );
+
+        pw.println( "<script type=\"text/javascript\">" );
+        pw.println( "shellCommandFocus();" );
+        pw.println( "</script>" );
+    }
+
+
+    protected ShellService getShellService()
+    {
+        return ( ( ShellService ) shellTracker.getService() );
+    }
+
+
+    public void activate( BundleContext bundleContext )
+    {
+        super.activate( bundleContext );
+
+        shellTracker = new ServiceTracker( bundleContext, ShellService.class.getName(), null );
+        shellTracker.open();
+    }
+
+
+    public void deactivate()
+    {
+        if ( shellTracker != null )
+        {
+            shellTracker.close();
+            shellTracker = null;
+        }
+
+        super.deactivate();
+    }
+
+
+    protected String escapeHtml( String text )
+    {
+        StringBuffer sb = new StringBuffer();
+        for ( int i = 0; i < text.length(); i++ )
+        {
+            char ch = text.charAt( i );
+            if ( ch == '<' )
+            {
+                sb.append( "&lt;" );
+            }
+            else if ( ch == '>' )
+            {
+                sb.append( "&gt;" );
+            }
+            else if ( ch == '&' )
+            {
+                sb.append( "&amp;" );
+            }
+            else if ( ch == ' ' )
+            {
+                sb.append( "&nbsp;" );
+            }
+            else if ( ch == '\r' )
+            {
+            }
+            else if ( ch == '\n' )
+            {
+                sb.append( "<br />\r\n" );
+            }
+            else
+            {
+                sb.append( ch );
+            }
+        }
+
+        return ( sb.toString() );
+    }
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index 59fbd7c..f438c03 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -48,6 +48,7 @@
 import org.apache.felix.webconsole.internal.core.SetStartLevelAction;
 import org.apache.felix.webconsole.internal.misc.ConfigurationRender;
 import org.apache.felix.webconsole.internal.misc.LicenseServlet;
+import org.apache.felix.webconsole.internal.misc.ShellServlet;
 import org.apache.felix.webconsole.internal.obr.BundleRepositoryRender;
 import org.apache.felix.webconsole.internal.obr.RefreshRepoAction;
 import org.apache.felix.webconsole.internal.system.GCAction;
@@ -129,7 +130,7 @@
         { ComponentConfigurationPrinter.class, ComponentsServlet.class, ConfigManager.class, BundlesServlet.class,
             InstallAction.class, SetStartLevelAction.class, ConfigurationRender.class, GCAction.class,
             ShutdownAction.class, ShutdownRender.class, VMStatRender.class, BundleRepositoryRender.class,
-            LicenseServlet.class, RefreshRepoAction.class };
+            LicenseServlet.class, RefreshRepoAction.class, ShellServlet.class };
 
     private BundleContext bundleContext;
 
@@ -320,7 +321,7 @@
         {
             req.setAttribute( ATTR_LABEL_MAP, labelMap );
             req.setAttribute( ATTR_APP_ROOT, request.getContextPath() + request.getServletPath() );
-            
+
             plugin.service( req, res );
         }
         else
diff --git a/webconsole/src/main/resources/res/ui/shell.css b/webconsole/src/main/resources/res/ui/shell.css
new file mode 100644
index 0000000..111f8c0
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/shell.css
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+div.consolebuttons {
+    text-align: right;
+    border: none;
+    width: 955px;
+    padding-bottom: 2px;
+}
+
+div.consoleframe {
+    font-family: "Courier New", Courier, monospace;
+    font-size: 12px;
+    font-weight: normal;
+    background-color: #f0f0f0;
+    color: #000000;
+    border: 1px solid #999999;
+    width: 955px;
+    height: 500px;
+    overflow: auto;
+}
+
+div.console {
+}
+
+span.consolecommand {
+    white-space: pre;
+}
+
+span.error {
+    color: #ff0000;
+}
+
+span.prompt {
+    padding-left: 0px;
+    padding-top: 2px;
+    padding-bottom: 3px;
+}
+
+input.command {
+    font-family: "Courier New", Courier, monospace;
+    font-size: 12px;
+    font-weight: normal;
+    background-color: #f0f0f0;
+    color: #000000;
+    border: none;
+    width: 900px;
+    margin-left: -1px; 
+    margin-top: -1px; 
+}
diff --git a/webconsole/src/main/resources/res/ui/shell.js b/webconsole/src/main/resources/res/ui/shell.js
new file mode 100644
index 0000000..a36257c
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/shell.js
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function executeCommand(command) {
+    var xmlhttp = getXmlHttp();
+    if (!xmlhttp) {
+        return;
+    }
+    
+    if (xmlhttp.readyState < 4) {
+        xmlhttp.abort();
+    }
+    
+    var url = document.location;
+    
+    xmlhttp.open("POST", url);
+    
+    // set If-Modified-Since way back in the past to prevent
+    // using any content from the cache
+    xmlhttp.setRequestHeader("If-Modified-Since", new Date(0));
+    xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
+    
+    xmlhttp.onreadystatechange = updateConsole;
+    
+    xmlhttp.send("command=" + encodeURIComponent(command));
+}
+
+function updateConsole() {
+    var xmlhttp = getXmlHttp();
+    if (!xmlhttp || xmlhttp.readyState != 4) {
+        return;
+    }
+    
+    var result = xmlhttp.responseText;
+    if (!result) {
+        return;
+    }
+
+    var console = document.getElementById("console");
+    
+    console.style.display = "";
+    console.innerHTML = console.innerHTML + result;
+    
+    var consoleframe = document.getElementById("consoleframe");
+    consoleframe.scrollTop = console.scrollHeight;
+
+    document.forms["shellCommandForm"].elements["command"].value = "";
+    
+    shellCommandFocus();
+}
+
+function clearConsole() {
+    var console = document.getElementById("console");
+
+    console.style.display = "none";
+    console.innerHTML = "";
+    
+    var consoleframe = document.getElementById("consoleframe");
+    consoleframe.scrollTop = 0;
+    
+    shellCommandFocus();
+}
+
+function shellCommandFocus() {
+    document.forms["shellCommandForm"].elements["command"].focus();
+}
+
+function runShellCommand() {
+    var command = document.forms["shellCommandForm"].elements["command"].value;
+    
+    executeCommand(command);
+}