blob: 7c033dca137efa53ebee87e19990e77151d2a1d5 [file] [log] [blame]
/*
* 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.plugins.scriptconsole.internal;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.IOUtils;
import org.apache.felix.utils.json.JSONWriter;
import org.apache.felix.webconsole.AbstractWebConsolePlugin;
import org.apache.felix.webconsole.DefaultVariableResolver;
import org.apache.felix.webconsole.SimpleWebConsolePlugin;
import org.apache.felix.webconsole.WebConsoleUtil;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.log.LogService;
import javax.script.*;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
class ScriptConsolePlugin extends SimpleWebConsolePlugin
{
public static final String NAME = "sc";
private static final String TITLE = "%script.title";
private static final String CATEGORY = "Web Console";
private static final String[] CSS = { "/res/ui/codemirror/lib/codemirror.css",
"/res/ui/script-console.css" };
private final String TEMPLATE;
private final Logger log;
private final ScriptEngineManager scriptEngineManager;
private final ServiceRegistration registration;
public ScriptConsolePlugin(BundleContext bundleContext, Logger logger, ScriptEngineManager scriptEngineManager)
{
super(NAME, TITLE, processFileNames(CSS));
this.log = logger;
this.scriptEngineManager = scriptEngineManager;
TEMPLATE = readTemplateFile("/templates/script-console.html");
super.activate(bundleContext);
Properties props = new Properties();
props.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
props.put(Constants.SERVICE_DESCRIPTION, "Script Console Web Console Plugin");
props.put("felix.webconsole.label", ScriptConsolePlugin.NAME);
props.put("felix.webconsole.title", "Script Console");
registration = getBundleContext().registerService(Servlet.class.getName(), this,
props);
}
public String getCategory()
{
return CATEGORY;
}
@SuppressWarnings("unchecked")
@Override
protected void renderContent(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
final PrintWriter pw = response.getWriter();
DefaultVariableResolver varResolver = (DefaultVariableResolver) WebConsoleUtil.getVariableResolver(request);
varResolver.put("__scriptConfig__", getScriptConfig());
pw.println(TEMPLATE);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
final String contentType = getContentType(req);
resp.setContentType(contentType);
if (contentType.startsWith("text/"))
{
resp.setCharacterEncoding("UTF-8");
}
final String script = getCodeValue(req);
final Bindings bindings = new SimpleBindings();
final PrintWriter pw = resp.getWriter();
final ScriptHelper osgi = new ScriptHelper(getBundleContext());
final Writer errorWriter = new LogWriter(log);
final Reader reader = new StringReader(script)
;
//Populate bindings
bindings.put("request", req);
bindings.put("reader", reader);
bindings.put("response", resp);
bindings.put("out", pw);
bindings.put("osgi", osgi);
//Also expose the bundleContext to simplify scripts interaction with the
//enclosing OSGi container
bindings.put("bundleContext", getBundleContext());
final String lang = WebConsoleUtil.getParameter(req, "lang");
final boolean webClient = "webconsole".equals(WebConsoleUtil.getParameter(req,
"client"));
SimpleScriptContext sc = new SimpleScriptContext();
sc.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
sc.setWriter(pw);
sc.setErrorWriter(errorWriter);
sc.setReader(reader);
try
{
log.log(LogService.LOG_DEBUG, "Executing script" + script);
eval(script, lang, sc);
}
catch (Throwable t)
{
if (!webClient)
{
resp.setStatus(500);
}
pw.println(exceptionToString(t));
log.log(LogService.LOG_ERROR, "Error in executing script", t);
}
finally
{
osgi.cleanup();
}
}
private void eval(String script, String lang,ScriptContext ctx) throws ScriptException, IOException {
ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension(lang);
if(scriptEngine == null)
{
throw new IllegalArgumentException("No ScriptEngineFactory found for extension "+ lang);
}
// evaluate the script
//Currently we do not make use of returned object
final Object ignored = scriptEngine.eval(script, ctx);
// allways flush the error channel
ctx.getErrorWriter().flush();
}
private String getCodeValue(HttpServletRequest req) throws IOException
{
String script = WebConsoleUtil.getParameter(req, "code");
if (script == null)
{
script = getContentFromFilePart(req, "code");
}
if (script == null)
{
throw new IllegalArgumentException("'code' parameter not passed");
}
return script;
}
private String getContentType(HttpServletRequest req)
{
String passedContentType = WebConsoleUtil.getParameter(req, "responseContentType");
if (passedContentType != null)
{
return passedContentType;
}
return req.getPathInfo().endsWith(".json") ? "application/json" : "text/plain";
}
private String exceptionToString(Throwable t)
{
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
private static String[] processFileNames(String[] cssFiles)
{
String[] css = new String[cssFiles.length];
for (int i = 0; i < cssFiles.length; i++)
{
css[i] = '/' + NAME + CSS[i];
}
return css;
}
private String getScriptConfig()
{
try
{
return getScriptConfig0();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private String getScriptConfig0() throws IOException
{
StringWriter sw = new StringWriter();
JSONWriter jw = new JSONWriter(sw);
jw.array();
for (ScriptEngineFactory sef : scriptEngineManager.getEngineFactories())
{
jw.object();
if (sef.getExtensions().isEmpty())
{
continue;
}
jw.key("langName").value(sef.getLanguageName());
jw.key("langCode").value(sef.getExtensions().get(0));
//Language mode as per CodeMirror names
String mode = determineMode(sef.getExtensions());
if (mode != null)
{
jw.key("mode").value(mode);
}
jw.endObject();
}
jw.endArray();
return sw.toString();
}
private String determineMode(List<String> extensions)
{
if (extensions.contains("groovy"))
{
return "groovy";
}
else if (extensions.contains("esp"))
{
return "javascript";
}
return null;
}
private String getContentFromFilePart(HttpServletRequest req, String paramName)
throws IOException
{
String value = WebConsoleUtil.getParameter(req, paramName);
if (value != null)
{
return value;
}
final Map params = (Map) req.getAttribute(AbstractWebConsolePlugin.ATTR_FILEUPLOAD);
if (params == null)
{
return null;
}
FileItem[] codeFile = getFileItems(params, paramName);
if (codeFile.length == 0)
{
return null;
}
InputStream is = null;
try
{
is = codeFile[0].getInputStream();
StringWriter sw = new StringWriter();
IOUtils.copy(is, sw, "utf-8");
return sw.toString();
}
finally
{
IOUtils.closeQuietly(is);
}
}
private FileItem[] getFileItems(Map params, String name)
{
final List<FileItem> files = new ArrayList<FileItem>();
FileItem[] items = (FileItem[]) params.get(name);
if (items != null)
{
for (int i = 0; i < items.length; i++)
{
if (!items[i].isFormField() && items[i].getSize() > 0)
{
files.add(items[i]);
}
}
}
return files.toArray(new FileItem[files.size()]);
}
public void dispose()
{
super.deactivate();
if (registration != null)
{
registration.unregister();
}
}
}