/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.script.js; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.logging.Logger; import javax.script.ScriptException; import org.geoserver.script.js.engine.CommonJSEngine; import org.geotools.util.logging.Logging; import org.mozilla.javascript.BoundFunction; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.FunctionObject; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Response; import org.restlet.data.Status; import org.restlet.resource.OutputRepresentation; public class JsgiResponse { private int status = 200; private ScriptableObject headers; private Scriptable body; private Function forEach; static Logger LOGGER = Logging.getLogger("org.geoserver.script.js"); public JsgiResponse(Scriptable obj, Scriptable scope) { // extract status Object statusObj = obj.get("status", obj); if (statusObj instanceof Integer) { status = (Integer) statusObj; } // extract headers Object headersObj = obj.get("headers", obj); if (headersObj instanceof ScriptableObject) { headers = (ScriptableObject) headersObj; } // extract body Object bodyObj = obj.get("body", obj); if (bodyObj instanceof String) { // be lenient and convert strings to arrays Object[] array = {(String) bodyObj}; Context cx = CommonJSEngine.enterContext(); try { bodyObj = cx.newArray(scope, array); } finally { Context.exit(); } } if (bodyObj instanceof Scriptable) { body = (Scriptable) bodyObj; Object forEachObj = body.get("forEach", body); if (forEachObj instanceof Function) { forEach = (Function) forEachObj; } else { NativeArray bodyArray = null; if (body instanceof NativeArray) { bodyArray = (NativeArray) body; } if (bodyArray != null) { forEach = (Function) bodyArray.getPrototype().get("forEach", bodyArray); } } } if (forEach == null) { throw new RuntimeException("JSGI app must return an object with a 'body' member that has a 'forEach' function."); } } public void commit(Response response, final Scriptable scope) throws SecurityException, NoSuchMethodException { // set response status response.setStatus(new Status(status)); // set response headers Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers"); if (responseHeaders == null) { responseHeaders = new Form(); response.getAttributes().put("org.restlet.http.headers", responseHeaders); } if (headers != null) { for (Object id : headers.getIds()) { String name = id.toString(); String value = headers.get(name, headers).toString(); if (!name.equalsIgnoreCase("content-type")) { responseHeaders.add(name, value); } } } // write response body MediaType mediaType; String type = responseHeaders.getFirstValue("content-type", true); if (type == null) { mediaType = MediaType.TEXT_PLAIN; } else { mediaType = new MediaType(type); } final Method writeMethod = getClass().getDeclaredMethod("write", Context.class, Scriptable.class, Object[].class, Function.class); response.setEntity(new OutputRepresentation(mediaType) { @Override public void write(OutputStream outputStream) throws IOException { Context cx = CommonJSEngine.enterContext(); FunctionObject writeFunc = new FunctionObject("bodyWriter", writeMethod, scope); BoundFunction boundWrite = new BoundFunction(cx, scope, writeFunc, body, new Object[] {outputStream}); Object[] args = {boundWrite}; try { forEach.call(cx, scope, body, args); } finally { Context.exit(); outputStream.close(); } } }); } public static Object write(Context cx, Scriptable thisObj, Object[] args, Function func) throws ScriptException { OutputStream outputStream = (OutputStream) args[0]; Object part = args[1]; byte[] bytes = null; if (part instanceof String) { bytes = ((String) part).getBytes(); } else { LOGGER.severe("Unsupported response body type: " + part.toString()); } if (bytes != null) { try { outputStream.write(bytes); } catch (IOException e) { throw new ScriptException("Failed to write to body."); } } return null; } }