| /* |
| * 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.internal; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| |
| import javax.script.ScriptEngine; |
| import javax.script.ScriptEngineFactory; |
| |
| import org.apache.sling.commons.classloader.DynamicClassLoaderManager; |
| import org.apache.sling.commons.osgi.PropertiesUtil; |
| import org.apache.sling.scripting.api.AbstractScriptEngineFactory; |
| import org.apache.sling.scripting.api.ScriptCache; |
| import org.apache.sling.scripting.javascript.RhinoHostObjectProvider; |
| import org.apache.sling.scripting.javascript.SlingWrapper; |
| import org.apache.sling.scripting.javascript.helper.SlingContextFactory; |
| import org.apache.sling.scripting.javascript.helper.SlingWrapFactory; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableCalendar; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableItemMap; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableMap; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableNode; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptablePrintWriter; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableProperty; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableResource; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableVersion; |
| import org.apache.sling.scripting.javascript.wrapper.ScriptableVersionHistory; |
| import org.mozilla.javascript.Context; |
| import org.mozilla.javascript.ContextFactory; |
| import org.mozilla.javascript.ImporterTopLevel; |
| import org.mozilla.javascript.NativeJavaClass; |
| import org.mozilla.javascript.NativeJavaPackage; |
| import org.mozilla.javascript.Scriptable; |
| import org.mozilla.javascript.ScriptableObject; |
| import org.mozilla.javascript.tools.debugger.ScopeProvider; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.service.component.ComponentContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Deactivate; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @Component( |
| service = ScriptEngineFactory.class, |
| property = { |
| Constants.SERVICE_DESCRIPTION + "=Apache Sling Rhino Javascript Engine Factory", |
| Constants.SERVICE_VENDOR + "=The Apache Software Foundation", |
| "extensions=" + RhinoJavaScriptEngineFactory.ECMA_SCRIPT_EXTENSION, |
| "extensions=" + RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION, |
| "mimeTypes=text/ecmascript", |
| "mimeTypes=text/javascript", |
| "mimeTypes=application/ecmascript", |
| "mimeTypes=application/javascript", |
| "names=rhino", |
| "names=Rhino", |
| "names=javascript", |
| "names=JavaScript", |
| "names=ecmascript", |
| "names=ECMAScript" |
| }, |
| reference = @Reference( |
| name = "HostObjectProvider", |
| service = RhinoHostObjectProvider.class, |
| cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC, |
| bind = "addHostObjectProvider", |
| unbind = "removeHostObjectProvider" |
| ) |
| ) |
| @Designate( |
| ocd = RhinoJavaScriptEngineFactoryConfiguration.class |
| ) |
| public class RhinoJavaScriptEngineFactory extends AbstractScriptEngineFactory implements ScopeProvider { |
| |
| public final static int DEFAULT_OPTIMIZATION_LEVEL = 9; |
| |
| public final static String ECMA_SCRIPT_EXTENSION = "ecma"; |
| |
| public final static String ESP_SCRIPT_EXTENSION = "esp"; |
| |
| private static final Class<?>[] HOSTOBJECT_CLASSES = { |
| ScriptableResource.class, ScriptableNode.class, |
| ScriptableProperty.class, ScriptableItemMap.class, |
| ScriptablePrintWriter.class, ScriptableVersionHistory.class, |
| ScriptableVersion.class, ScriptableCalendar.class, ScriptableMap.class |
| }; |
| |
| /** |
| * default log |
| */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private int optimizationLevel; |
| |
| private static final int RHINO_LANGUAGE_VERSION = Context.VERSION_ES6; |
| private static final String LANGUAGE_VERSION = "partial ECMAScript 2015 support"; |
| private static final String LANGUAGE_NAME = "ECMAScript"; |
| |
| |
| private SlingWrapFactory wrapFactory; |
| |
| private Scriptable rootScope; |
| |
| private final Set<RhinoHostObjectProvider> hostObjectProvider = new HashSet<RhinoHostObjectProvider>(); |
| |
| @Reference |
| private DynamicClassLoaderManager dynamicClassLoaderManager = null; |
| |
| @Reference |
| private ScriptCache scriptCache = null; |
| |
| public ScriptEngine getScriptEngine() { |
| return new RhinoJavaScriptEngine(this, getRootScope(), scriptCache); |
| } |
| |
| public String getLanguageName() { |
| return LANGUAGE_NAME; |
| } |
| |
| public String getLanguageVersion() { |
| return LANGUAGE_VERSION; |
| } |
| |
| /** |
| * Get the optimization level that should be used when running JS scripts |
| * with Rhino |
| * |
| * @return an integer from 0-9 with 9 being the most aggressive optimization, or |
| * -1 if interpreted mode is to be used |
| */ |
| int getOptimizationLevel() { |
| return optimizationLevel; |
| } |
| |
| int rhinoLanguageVersion() { |
| return Context.VERSION_ES6; |
| } |
| |
| public Object getParameter(String name) { |
| if ("THREADING".equals(name)) { |
| return "MULTITHREADED"; |
| } |
| |
| return super.getParameter(name); |
| } |
| |
| public Scriptable getScope() { |
| return getRootScope(); |
| } |
| |
| SlingWrapFactory getWrapFactory() { |
| return wrapFactory; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Scriptable getRootScope() { |
| if (rootScope == null) { |
| |
| final Context rhinoContext = Context.enter(); |
| try { |
| rhinoContext.setOptimizationLevel(optimizationLevel); |
| rhinoContext.setLanguageVersion(RHINO_LANGUAGE_VERSION); |
| Scriptable tmpScope = rhinoContext.initStandardObjects(new ImporterTopLevel(rhinoContext), false); |
| |
| // default classes |
| addHostObjects(tmpScope, (Class<? extends ScriptableObject>[]) HOSTOBJECT_CLASSES); |
| |
| // provided classes |
| for (RhinoHostObjectProvider provider : hostObjectProvider) { |
| addHostObjects(tmpScope, provider.getHostObjectClasses()); |
| addImportedClasses(rhinoContext, tmpScope, provider.getImportedClasses()); |
| addImportedPackages(rhinoContext, tmpScope, provider.getImportedPackages()); |
| } |
| |
| // only assign the root scope when complete set up |
| rootScope = tmpScope; |
| |
| } finally { |
| // ensure the context is exited after setting up the |
| // the new root scope |
| Context.exit(); |
| } |
| } |
| |
| return rootScope; |
| } |
| |
| private void dropRootScope() { |
| |
| // ensure the debugger is closed if the root scope will |
| // be replaced to ensure no references to the old scope |
| // and context remain |
| ContextFactory contextFactory = ContextFactory.getGlobal(); |
| if (contextFactory instanceof SlingContextFactory) { |
| ((SlingContextFactory) contextFactory).exitDebugger(); |
| } |
| |
| // drop the scope |
| rootScope = null; |
| } |
| |
| // ---------- SCR integration |
| @Activate |
| protected void activate(final ComponentContext context, final RhinoJavaScriptEngineFactoryConfiguration configuration) { |
| Dictionary<?, ?> props = context.getProperties(); |
| boolean debugging = getProperty("org.apache.sling.scripting.javascript.debug", props, context.getBundleContext(), false); |
| |
| // try to get the manifest |
| String rhinoVersion = null; |
| InputStream ins = null; |
| try { |
| Enumeration<URL> resources = RhinoJavaScriptEngineFactory.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); |
| while (resources.hasMoreElements()) { |
| try { |
| URL url = resources.nextElement(); |
| ins = url.openStream(); |
| if (ins != null) { |
| Manifest manifest = new Manifest(ins); |
| Attributes attrs = manifest.getMainAttributes(); |
| rhinoVersion = attrs.getValue("Rhino-Version"); |
| if (rhinoVersion != null) { |
| break; |
| } |
| } |
| } finally { |
| if (ins != null) { |
| try { |
| ins.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| } |
| } catch (IOException ioe) { |
| log.warn("Unable to read Rhino version.", ioe); |
| } |
| |
| optimizationLevel = readOptimizationLevel(configuration); |
| |
| // setup the wrap factory |
| wrapFactory = new SlingWrapFactory(); |
| |
| // initialize the Rhino Context Factory |
| SlingContextFactory.setup(this, RHINO_LANGUAGE_VERSION); |
| |
| setEngineName(getEngineName() + " (Rhino " + (rhinoVersion != null ? rhinoVersion : "unknown") + ")"); |
| |
| setExtensions(PropertiesUtil.toStringArray(props.get("extensions"))); |
| setMimeTypes(PropertiesUtil.toStringArray(props.get("mimeTypes"))); |
| setNames(PropertiesUtil.toStringArray(props.get("names"))); |
| |
| final ContextFactory contextFactory = ContextFactory.getGlobal(); |
| if (contextFactory instanceof SlingContextFactory) { |
| ((SlingContextFactory) contextFactory).setDebugging(debugging); |
| } |
| // set the dynamic class loader as the application class loader |
| final DynamicClassLoaderManager dclm = this.dynamicClassLoaderManager; |
| if (dclm != null) { |
| contextFactory.initApplicationClassLoader(dynamicClassLoaderManager.getDynamicClassLoader()); |
| } |
| |
| log.info("Activated with optimization level {}", optimizationLevel); |
| } |
| |
| @Deactivate |
| @SuppressWarnings("unused") |
| protected void deactivate(ComponentContext context) { |
| |
| // remove the root scope |
| dropRootScope(); |
| |
| // remove our context factory |
| SlingContextFactory.teardown(); |
| |
| // remove references |
| wrapFactory = null; |
| hostObjectProvider.clear(); |
| } |
| |
| @SuppressWarnings("unused") |
| protected void addHostObjectProvider(RhinoHostObjectProvider provider) { |
| hostObjectProvider.add(provider); |
| |
| if (rootScope != null) { |
| addHostObjects(rootScope, provider.getHostObjectClasses()); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| protected void removeHostObjectProvider(RhinoHostObjectProvider provider) { |
| // remove the current root scope and have it recreated using the |
| // new host object classes |
| if (hostObjectProvider.remove(provider)) { |
| dropRootScope(); |
| } |
| } |
| |
| // ---------- internal |
| |
| private void addHostObjects(Scriptable scope, Class<? extends Scriptable>[] classes) { |
| if (classes != null) { |
| for (Class<? extends Scriptable> clazz : classes) { |
| try { |
| |
| // register the host object |
| ScriptableObject.defineClass(scope, clazz); |
| |
| if (SlingWrapper.class.isAssignableFrom(clazz)) { |
| |
| // SlingWrappers can map to several classes if needed |
| final SlingWrapper hostWrapper = (SlingWrapper) clazz.newInstance(); |
| for (Class<?> c : hostWrapper.getWrappedClasses()) { |
| getWrapFactory().registerWrapper(c, hostWrapper.getClassName()); |
| } |
| } else { |
| // but other Scriptable host objects need to be |
| // registered as well |
| final Scriptable host = clazz.newInstance(); |
| getWrapFactory().registerWrapper(host.getClass(), host.getClassName()); |
| } |
| } catch (Throwable t) { |
| log.warn("addHostObjects: Cannot prepare host object " + clazz, t); |
| } |
| } |
| } |
| } |
| |
| private void addImportedClasses(Context cx, Scriptable scope, |
| Class<?>[] classes) { |
| if (classes != null && classes.length > 0) { |
| NativeJavaClass[] np = new NativeJavaClass[classes.length]; |
| for (int i = 0; i < classes.length; i++) { |
| np[i] = new NativeJavaClass(scope, classes[i]); |
| } |
| ScriptableObject.callMethod(cx, scope, "importClass", np); |
| } |
| } |
| |
| private void addImportedPackages(Context cx, Scriptable scope, |
| String[] packages) { |
| if (packages != null && packages.length > 0) { |
| NativeJavaPackage[] np = new NativeJavaPackage[packages.length]; |
| for (int i = 0; i < packages.length; i++) { |
| np[i] = new NativeJavaPackage(packages[i]); |
| } |
| ScriptableObject.callMethod(cx, scope, "importPackage", np); |
| } |
| } |
| |
| private boolean getProperty(String name, Dictionary<?, ?> props, |
| BundleContext bundleContext, boolean defaultValue) { |
| Object value = props.get(name); |
| if (value == null) { |
| value = bundleContext.getProperty(name); |
| } |
| |
| return (value != null) |
| ? Boolean.parseBoolean(String.valueOf(value)) |
| : defaultValue; |
| } |
| |
| private int readOptimizationLevel(final RhinoJavaScriptEngineFactoryConfiguration configuration) { |
| int optLevel = configuration.org_apache_sling_scripting_javascript_rhino_optLevel(); |
| if (!Context.isValidOptimizationLevel(optLevel)) { |
| log.warn("Invalid optimization level {}, using default value", optLevel); |
| optLevel = DEFAULT_OPTIMIZATION_LEVEL; |
| } |
| return optLevel; |
| } |
| } |