/* * 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.javascript.helper; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; import org.apache.sling.api.resource.NonExistingResource; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.api.scripting.SlingBindings; import org.apache.sling.api.scripting.SlingScriptHelper; import org.apache.sling.scripting.javascript.internal.RhinoJavaScriptEngineFactory; import org.apache.sling.scripting.javascript.io.EspReader; import org.mozilla.javascript.Context; import org.mozilla.javascript.IdFunctionCall; import org.mozilla.javascript.IdFunctionObject; import org.mozilla.javascript.Kit; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Wrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The <code>SlingGlobal</code> class provides two interesting new global * functions which are not part of the ECMAScript standard but which are * available in the Rhino Shell and which may be of use by JavaScripts: * <dl> * <dt><code>print(args, ...)</code></dt> * <dd>Prints the arguments <code>args</code> in a single message to the scripts * logger available as the global <em>log</em> variable.</dd> * <dt><code>load(args, ...)</code></dt> * <dd>Loads the scripts named as parameters into the current scope one, after * the other. Usually the script files are read as plain JavaScript files. If * the file extension happens to be <em>.esp</em> to indicate an ECMAScript * Server Page, the file is read through an * {@link org.apache.sling.scripting.javascript.io.EspReader}. Failure to read * one of the files throws an error.</dd> * </dl> */ public class SlingGlobal implements Serializable, IdFunctionCall { static final long serialVersionUID = 6080442165748707530L; private static final Object FTAG = new Object(); private static final int Id_load = 1; private static final int Id_print = 2; private static final int Id_require = 3; private static final int LAST_SCOPE_FUNCTION_ID = 3; /** default log */ private static final Logger defaultLog = LoggerFactory.getLogger(SlingGlobal.class); public static void init(Scriptable scope, boolean sealed) { SlingGlobal obj = new SlingGlobal(); for (int id = 1; id <= LAST_SCOPE_FUNCTION_ID; ++id) { String name; int arity = 1; switch (id) { case Id_load: name = "load"; break; case Id_print: name = "print"; break; case Id_require: name = "require"; break; default: throw Kit.codeBug(); } IdFunctionObject f = new IdFunctionObject(obj, FTAG, id, name, arity, scope); if (sealed) { f.sealObject(); } f.exportAsScopeProperty(); } } public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (f.hasTag(FTAG)) { int methodId = f.methodId(); switch (methodId) { case Id_load: { load(cx, thisObj, args); return Context.getUndefinedValue(); } case Id_print: { print(cx, thisObj, args); return Context.getUndefinedValue(); } case Id_require: { return require(cx, thisObj, args); } } } throw f.unknown(); } private void print(Context cx, Scriptable thisObj, Object[] args) { StringBuffer message = new StringBuffer(); for (int i = 0; i < args.length; i++) { if (i > 0) { message.append(" "); } // Convert the arbitrary JavaScript value into a string form. String s = ScriptRuntime.toString(args[i]); message.append(s); } getLogger(cx, thisObj).info(message.toString()); } private void load(Context cx, Scriptable thisObj, Object[] args) { SlingScriptHelper sling = getProperty(cx, thisObj, SlingBindings.SLING, SlingScriptHelper.class); if (sling == null) { throw new NullPointerException(SlingBindings.SLING); } Scriptable globalScope = ScriptableObject.getTopLevelScope(thisObj); Resource scriptResource = sling.getScript().getScriptResource(); ResourceResolver resolver = scriptResource.getResourceResolver(); // the path of the current script to resolve realtive paths String currentScript = sling.getScript().getScriptResource().getPath(); String scriptParent = ResourceUtil.getParent(currentScript); for (Object arg : args) { String scriptName = ScriptRuntime.toString(arg); Resource loadScript = null; if (!scriptName.startsWith("/")) { String absScriptName = scriptParent + "/" + scriptName; loadScript = resolver.resolve(absScriptName); } // not resolved relative to the current script if (loadScript == null) { loadScript = resolver.resolve(scriptName); } if (loadScript == null) { throw Context.reportRuntimeError("Script file " + scriptName + " not found"); } InputStream scriptStream = loadScript.adaptTo(InputStream.class); if (scriptStream == null) { throw Context.reportRuntimeError("Script file " + scriptName + " cannot be read from"); } try { // reader for the stream Reader scriptReader = new InputStreamReader(scriptStream, Charset.forName("UTF-8")); // check whether we have to wrap the basic reader if (scriptName .endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) { scriptReader = new EspReader(scriptReader); } // read the suff buffered for better performance scriptReader = new BufferedReader(scriptReader); // now, let's go cx.evaluateReader(globalScope, scriptReader, scriptName, 1, null); } catch (IOException ioe) { throw Context.reportRuntimeError("Failure reading file " + scriptName + ": " + ioe); } finally { // ensure the script input stream is closed try { scriptStream.close(); } catch (IOException ignore) { } } } } public Object require(Context cx, Scriptable thisObj, Object[] args) { if (args.length != 1 || !(args[0] instanceof String)) { throw Context .reportRuntimeError("require() requires a String argument"); } String modulePath = (String) args[0]; ModuleScope moduleScope = null; if (thisObj instanceof ModuleScope) { moduleScope = (ModuleScope) thisObj; } ModuleScope module = loadModule(cx, modulePath.trim(), moduleScope, thisObj); return module.getExports(); } private ModuleScope loadModule(Context cx, String modulePath, ModuleScope moduleScope, Scriptable thisObj) { String absolutePath = modulePath; if (modulePath.startsWith(".")) { // relative if (moduleScope == null) { throw Context .reportRuntimeError("Cannot resolve relative module name outside of a module scope."); } absolutePath = (moduleScope.getModuleName() + "/" + modulePath) .replaceAll("[^/]*/\\./", ""); while (absolutePath.matches("([^/]*/)?[^/]*/\\.\\./")) { absolutePath = absolutePath .replaceAll("([^/]*/)?[^/]*/\\.\\./", ""); } } absolutePath = absolutePath + ".js"; SlingScriptHelper sling = getProperty(cx, thisObj, SlingBindings.SLING, SlingScriptHelper.class); if (sling == null) { throw new NullPointerException(SlingBindings.SLING); } ResourceResolver resrev = sling.getScript().getScriptResource().getResourceResolver(); Resource script = null; String scriptName = null; for (String basepath : resrev.getSearchPath()) { script = resrev.resolve(basepath + absolutePath); if (script!=null&&!(script instanceof NonExistingResource)) { scriptName = basepath + absolutePath; break; } } if (script==null) { throw Context.reportRuntimeError("Unable to resolve module " + absolutePath + " in search path"); } InputStream scriptStream = script.adaptTo(InputStream.class); if (scriptStream == null) { //try once again scriptStream = resrev.resolve(scriptName).adaptTo(InputStream.class); if (scriptStream==null) { throw Context.reportRuntimeError("Script file " + script.getPath() + " cannot be read"); } } try { // reader for the stream Reader scriptReader = new InputStreamReader(scriptStream, Charset.forName("UTF-8")); // check whether we have to wrap the basic reader if (scriptName .endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) { scriptReader = new EspReader(scriptReader); } // read the suff buffered for better performance scriptReader = new BufferedReader(scriptReader); //TODO: execute script with ModuleScope // now, let's go ModuleScope scope = moduleScope; if (scope==null) { scope = new ModuleScope(thisObj, absolutePath.substring(0, absolutePath.length() - 3)); } else { scope.reset(); } cx.evaluateReader(scope, scriptReader, scriptName, 1, null); return scope; } catch (IOException ioe) { throw Context.reportRuntimeError("Failure reading file " + scriptName + ": " + ioe); } finally { // ensure the script input stream is closed try { scriptStream.close(); } catch (IOException ignore) { } } } /** * Returns the script logger or the logger of this class as a fallback * default if the global log variable is not accessible. */ private Logger getLogger(Context cx, Scriptable scope) { Logger log = getProperty(cx, scope, SlingBindings.LOG, Logger.class); if (log == null) { log = this.defaultLog; } return log; } /** * Returns the named toplevel property converted to the requested * <code>type</code> or <code>null</code> if no such property exists or the * property is of the wrong type. */ @SuppressWarnings("unchecked") private <Type> Type getProperty(Context cx, Scriptable scope, String name, Class<Type> type) { Object prop = ScriptRuntime.name(cx, scope, name); if (prop instanceof Wrapper) { prop = ((Wrapper) prop).unwrap(); } if (type.isInstance(prop)) { return (Type) prop; // unchecked case } return null; } }