blob: 44294cce001ff64adafa32e7c8155f9b9efa6921 [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.sling.scripting.console.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
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.apache.sling.api.resource.Resource;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.api.scripting.SlingScript;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
@Service
@Property(name = "felix.webconsole.label", value = ScriptConsolePlugin.NAME)
public class ScriptConsolePlugin extends SimpleWebConsolePlugin {
private Logger log = LoggerFactory.getLogger(getClass());
public static final String NAME = "scriptconsole";
private static final String TITLE = "%script.title";
private static final String[] CSS = {"/res/ui/codemirror/lib/codemirror.css","/res/ui/script-console.css"};
private final String TEMPLATE;
private BundleContext bundleContext;
@Reference
private ScriptEngineManager scriptEngineManager;
public ScriptConsolePlugin() {
super(NAME, TITLE, processFileNames(CSS));
TEMPLATE = readTemplateFile("/templates/script-console.html");
}
@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 SlingBindings bindings = new SlingBindings();
final PrintWriter pw = resp.getWriter();
//Populate bindings
bindings.put(SlingBindings.REQUEST, req);
bindings.put(SlingBindings.READER, new StringReader(script));
bindings.put(SlingBindings.RESPONSE, resp);
bindings.put(SlingBindings.OUT, pw);
//Also expose the bundleContext to simplify scripts interaction with the
//enclosing OSGi container
bindings.put("bundleContext", bundleContext);
final String lang = WebConsoleUtil.getParameter(req, "lang");
final Resource resource = new RuntimeScriptResource(lang, script);
final boolean webClient = "webconsole".equals(WebConsoleUtil.getParameter(req,"client"));
SlingScript slingScript = resource.adaptTo(SlingScript.class);
try {
log.debug("Executing script {}",script);
slingScript.eval(bindings);
} catch (Throwable t){
if(!webClient){
resp.setStatus(500);
}
pw.println(exceptionToString(t));
log.warn("Error in executing script",t);
}
}
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 files = new ArrayList();
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 (FileItem[]) files.toArray(new FileItem[files.size()]);
}
@Activate
public void activate(BundleContext bundleContext) {
super.activate(bundleContext);
this.bundleContext = bundleContext;
}
@Deactivate
public void deactivate() {
super.deactivate();
}
}