/* * 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(); } } }