You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:11:17 UTC
[sling-org-apache-sling-scripting-sightly-js-provider] 01/24:
SLING-4704 - Move the Sightly scripting engine artifacts from contrib to
bundles
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.scripting.sightly.js.provider-1.0.10
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-js-provider.git
commit 1cfbc86a98e708f1cbc7a0f28b39bfc02611f3eb
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Tue May 12 15:39:47 2015 +0000
SLING-4704 - Move the Sightly scripting engine artifacts from contrib to bundles
* moved Sightly artifacts from contrib to bundles
* updated SCM info
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider@1678979 13f79535-47bb-0310-9956-ffa450edef68
---
README.md | 8 +
pom.xml | 171 ++
.../sling/scripting/sightly/js/impl/Console.java | 49 +
.../scripting/sightly/js/impl/JsEnvironment.java | 205 +++
.../scripting/sightly/js/impl/JsUseProvider.java | 108 ++
.../sling/scripting/sightly/js/impl/Utils.java | 46 +
.../sling/scripting/sightly/js/impl/Variables.java | 34 +
.../sightly/js/impl/async/AsyncContainer.java | 95 +
.../sightly/js/impl/async/AsyncExtractor.java | 95 +
.../impl/async/TimingBindingsValuesProvider.java | 41 +
.../sightly/js/impl/async/TimingFunction.java | 54 +
.../sightly/js/impl/async/UnaryCallback.java | 31 +
.../sightly/js/impl/cjs/CommonJsModule.java | 69 +
.../sightly/js/impl/cjs/ExportsObject.java | 46 +
.../js/impl/jsapi/SlyBindingsValuesProvider.java | 311 ++++
.../scripting/sightly/js/impl/loop/EventLoop.java | 78 +
.../sightly/js/impl/loop/EventLoopInterop.java | 53 +
.../sling/scripting/sightly/js/impl/loop/Task.java | 43 +
.../sightly/js/impl/rhino/HybridObject.java | 163 ++
.../scripting/sightly/js/impl/rhino/JsUtils.java | 46 +
.../sightly/js/impl/rhino/JsValueAdapter.java | 164 ++
.../sightly/js/impl/use/DependencyResolver.java | 57 +
.../scripting/sightly/js/impl/use/UseFunction.java | 140 ++
.../SLING-INF/libs/sling/sightly/js/3rd-party/q.js | 1935 ++++++++++++++++++++
.../libs/sling/sightly/js/internal/helper.js | 43 +
.../libs/sling/sightly/js/internal/promise.js | 36 +
.../libs/sling/sightly/js/internal/request.js | 140 ++
.../libs/sling/sightly/js/internal/resource.js | 166 ++
.../libs/sling/sightly/js/internal/sly.js | 59 +
29 files changed, 4486 insertions(+)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..173e8d3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+Apache Sling Scripting Sightly JavaScript Use Provider
+=====================================
+
+This bundle allows Sightly's USE API to access JS scripts. It also wraps Sling's JS engine in a simulated event loop.
+
+The bundle also contains a bindings values provider that adds an API layer accessible from Sightly & JS.
+
+The implementation of that API can be found in `src/main/resources/SLING-INF`.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..88d6c57
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <!-- ======================================================================= -->
+ <!-- P A R E N T P R O J E C T -->
+ <!-- ======================================================================= -->
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>22</version>
+ <relativePath />
+ </parent>
+
+ <!-- ======================================================================= -->
+ <!-- P R O J E C T -->
+ <!-- ======================================================================= -->
+ <artifactId>org.apache.sling.scripting.sightly.js.provider</artifactId>
+ <version>1.0.5-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Scripting Sightly JavaScript Use Provider</name>
+
+ <description>
+ The Apache Sling Sightly JavaScript Use Provider adds support for accessing JS scripts from Sightly's Use-API.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</developerConnection>
+ <url>http://svn.apache.org/repos/asf/sling/trunk/bundles/scripting/sightly/js-use-provider</url>
+ </scm>
+
+ <properties>
+ <sling.java.version>6</sling.java.version>
+ </properties>
+
+ <!-- ======================================================================= -->
+ <!-- B U I L D -->
+ <!-- ======================================================================= -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Sling-Bundle-Resources>/libs/sling/sightly/js;path:=/SLING-INF/libs/sling/sightly/js</Sling-Bundle-Resources>
+ <Require-Capability>io.sightly; filter:="(&(version>=1.0)(!(version>=2.0)))"</Require-Capability>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <!-- ======================================================================= -->
+ <!-- D E P E N D E N C I E S -->
+ <!-- ======================================================================= -->
+ <dependencies>
+ <!-- OSGI -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Sling -->
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.4.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.scripting.api</artifactId>
+ <version>2.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.scripting.javascript</artifactId>
+ <version>2.0.12</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.jcr.resource</artifactId>
+ </exclusion>
+ </exclusions>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Sightly -->
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.scripting.sightly</artifactId>
+ <version>1.0.3-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Apache Commons -->
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+
+
+ <!-- Rhino -->
+ <dependency>
+ <groupId>org.mozilla</groupId>
+ <artifactId>rhino</artifactId>
+ <version>1.7R4</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java
new file mode 100644
index 0000000..355e253
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Console.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.sightly.js.impl;
+
+import org.slf4j.Logger;
+
+/**
+ * The console object, used for logging
+ */
+public class Console {
+
+ private final Logger logger;
+
+ public Console(Logger logger) {
+ this.logger = logger;
+ }
+
+ public void log(String msg) {
+ logger.info(msg);
+ }
+
+ public void warn(String msg) {
+ logger.warn(msg);
+ }
+
+ public void error(String msg) {
+ logger.error(msg);
+ }
+
+ public void debug(String msg) {
+ logger.debug(msg);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
new file mode 100644
index 0000000..b2d096a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * 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.sightly.js.impl;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+import javax.script.SimpleScriptContext;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+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.sightly.ResourceResolution;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+import org.apache.sling.scripting.sightly.js.impl.cjs.CommonJsModule;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoop;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.loop.Task;
+import org.apache.sling.scripting.sightly.js.impl.use.DependencyResolver;
+import org.apache.sling.scripting.sightly.js.impl.use.UseFunction;
+import org.mozilla.javascript.Context;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Environment for running JS scripts
+ */
+public class JsEnvironment {
+
+ private final ScriptEngine jsEngine;
+ private final Bindings engineBindings;
+ private EventLoop eventLoop;
+
+ public JsEnvironment(ScriptEngine jsEngine) {
+ this.jsEngine = jsEngine;
+ engineBindings = new SimpleBindings();
+ TimingBindingsValuesProvider.INSTANCE.addBindings(engineBindings);
+ }
+
+ public void initialize() {
+ Context context = Context.enter();
+ eventLoop = EventLoopInterop.obtainEventLoop(context);
+ }
+
+ public void cleanup() {
+ Context context = Context.getCurrentContext();
+ if (context == null) {
+ throw new IllegalStateException("No current context");
+ }
+ EventLoopInterop.cleanupEventLoop(context);
+ Context.exit();
+ }
+
+ /**
+ * Run a Js script at a given path
+ * @param caller the resource of the script that invokes the Js code
+ * @param path the path to the JS script
+ * @param globalBindings the global bindings for the script
+ * @param arguments the arguments from the use-plugin
+ * @param callback callback that will receive the result of the script
+ */
+ public void run(Resource caller, String path, Bindings globalBindings, Bindings arguments, UnaryCallback callback) {
+ Resource scriptResource = caller.getChild(path);
+ SlingScriptHelper scriptHelper = (SlingScriptHelper) globalBindings.get(SlingBindings.SLING);
+ Resource componentCaller = ResourceResolution.getResourceForRequest(caller.getResourceResolver(), scriptHelper.getRequest());
+ if (scriptResource == null) {
+ if (isResourceOverlay(caller, componentCaller)) {
+ scriptResource = ResourceResolution.getResourceFromSearchPath(componentCaller, path);
+ } else {
+ scriptResource = ResourceResolution.getResourceFromSearchPath(caller, path);
+ }
+ }
+ if (scriptResource == null) {
+ throw new SightlyException("Required script resource could not be located: " + path);
+ }
+ runResource(scriptResource, globalBindings, arguments, callback);
+ }
+
+ public void runResource(Resource scriptResource, Bindings globalBindings, Bindings arguments, UnaryCallback callback) {
+ ScriptContext scriptContext = new SimpleScriptContext();
+ CommonJsModule module = new CommonJsModule();
+ Bindings scriptBindings = buildBindings(scriptResource, globalBindings, arguments, module);
+ scriptContext.setBindings(scriptBindings, ScriptContext.ENGINE_SCOPE);
+ runScript(scriptResource, scriptContext, callback, module);
+ }
+
+ public AsyncContainer runResource(Resource scriptResource, Bindings globalBindings, Bindings arguments) {
+ AsyncContainer asyncContainer = new AsyncContainer();
+ runResource(scriptResource, globalBindings, arguments, asyncContainer.createCompletionCallback());
+ return asyncContainer;
+ }
+
+ /**
+ * Run a script at a given path
+ * @param caller the resource of the script that invokes the Js code
+ * @param path the path to the JS script
+ * @param globalBindings bindings for the JS script
+ * @return an asynchronous container for the result
+ * @throws UnsupportedOperationException if this method is run when the event loop is not empty
+ */
+ public AsyncContainer run(Resource caller, String path, Bindings globalBindings, Bindings arguments) {
+ AsyncContainer asyncContainer = new AsyncContainer();
+ run(caller, path, globalBindings, arguments, asyncContainer.createCompletionCallback());
+ return asyncContainer;
+ }
+
+ private Bindings buildBindings(Resource scriptResource, Bindings local, Bindings arguments, CommonJsModule commonJsModule) {
+ Bindings bindings = new SimpleBindings();
+ bindings.putAll(engineBindings);
+ DependencyResolver dependencyResolver = new DependencyResolver(scriptResource, this, local);
+ UseFunction useFunction = new UseFunction(dependencyResolver, arguments);
+ bindings.put(Variables.JS_USE, useFunction);
+ bindings.put(Variables.MODULE, commonJsModule);
+ bindings.put(Variables.EXPORTS, commonJsModule.getExports());
+ bindings.put(Variables.CONSOLE, new Console(LoggerFactory.getLogger(scriptResource.getName())));
+ bindings.putAll(local);
+ return bindings;
+ }
+
+ private void runScript(Resource scriptResource, ScriptContext scriptContext, UnaryCallback callback, CommonJsModule commonJsModule) {
+ eventLoop.schedule(scriptTask(scriptResource, scriptContext, callback, commonJsModule));
+ }
+
+ private Task scriptTask(final Resource scriptResource, final ScriptContext scriptContext,
+ final UnaryCallback callback, final CommonJsModule commonJsModule) {
+ return new Task(new Runnable() {
+ @Override
+ public void run() {
+ Reader reader = null;
+ try {
+ reader = new InputStreamReader(scriptResource.adaptTo(InputStream.class));
+ Object result = jsEngine.eval(reader, scriptContext);
+ if (commonJsModule.isModified()) {
+ result = commonJsModule.getExports();
+ }
+ if (result instanceof AsyncContainer) {
+ ((AsyncContainer) result).addListener(callback);
+ } else {
+ callback.invoke(result);
+ }
+ } catch (ScriptException e) {
+ throw new SightlyException(e);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ }
+ });
+ }
+
+ /**
+ * Using the inheritance chain created with the help of {@code sling:resourceSuperType} this method checks if {@code resourceB}
+ * inherits from {@code resourceA}. In case {@code resourceA} is a {@code nt:file}, its parent will be used for the inheritance check.
+ *
+ * @param resourceA the base resource
+ * @param resourceB the potentially overlaid resource
+ * @return {@code true} if {@code resourceB} overlays {@code resourceB}, {@code false} otherwise
+ */
+ private boolean isResourceOverlay(Resource resourceA, Resource resourceB) {
+ String resourceBSuperType = resourceB.getResourceSuperType();
+ if (StringUtils.isNotEmpty(resourceBSuperType)) {
+ ResourceResolver resolver = resourceA.getResourceResolver();
+ String parentResourceType = resourceA.getResourceType();
+ if ("nt:file".equals(parentResourceType)) {
+ parentResourceType = ResourceUtil.getParent(resourceA.getPath());
+ }
+ Resource parentB = resolver.getResource(resourceBSuperType);
+ while (parentB != null && !"/".equals(parentB.getPath()) && StringUtils.isNotEmpty(resourceBSuperType)) {
+ if (parentB.getPath().equals(parentResourceType)) {
+ return true;
+ }
+ resourceBSuperType = parentB.getResourceSuperType();
+ parentB = resolver.getResource(resourceBSuperType);
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java
new file mode 100644
index 0000000..ac2c886
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsUseProvider.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * 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.sightly.js.impl;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingException;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+import org.apache.sling.scripting.sightly.js.impl.jsapi.SlyBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsValueAdapter;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+import org.apache.sling.scripting.sightly.use.ProviderOutcome;
+import org.apache.sling.scripting.sightly.use.UseProvider;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Use provider for JS scripts. Ensures proper integration between Sightly & JS code-behind.
+ */
+@Component(
+ metatype = true,
+ label = "Apache Sling Scripting Sightly JavaScript Use Provider",
+ description = "The JavaScript Use Provider is responsible for instantiating JavaScript Use-API objects."
+)
+@Service(UseProvider.class)
+@Properties({
+ @Property(
+ name = Constants.SERVICE_RANKING,
+ label = "Service Ranking",
+ description = "The Service Ranking value acts as the priority with which this Use Provider is queried to return an " +
+ "Use-object. A higher value represents a higher priority.",
+ intValue = 80,
+ propertyPrivate = false
+ )
+})
+public class JsUseProvider implements UseProvider {
+
+ private static final String JS_ENGINE_NAME = "javascript";
+
+ private static final Logger log = LoggerFactory.getLogger(JsUseProvider.class);
+ private static final JsValueAdapter jsValueAdapter = new JsValueAdapter(new AsyncExtractor());
+
+ @Reference
+ private ScriptEngineManager scriptEngineManager = null;
+
+ @Reference
+ private SlyBindingsValuesProvider slyBindingsValuesProvider = null;
+
+ @Override
+ public ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments) {
+ Bindings globalBindings = renderContext.getBindings();
+ slyBindingsValuesProvider.processBindings(globalBindings);
+ if (!Utils.isJsScript(identifier)) {
+ return ProviderOutcome.failure();
+ }
+ ScriptEngine jsEngine = scriptEngineManager.getEngineByName(JS_ENGINE_NAME);
+ if (jsEngine == null) {
+ return ProviderOutcome.failure(new SightlyException("No JavaScript engine was defined."));
+ }
+ SlingScriptHelper scriptHelper = Utils.getHelper(globalBindings);
+ JsEnvironment environment = null;
+ try {
+ environment = new JsEnvironment(jsEngine);
+ environment.initialize();
+ String callerPath = scriptHelper.getScript().getScriptResource().getPath();
+ ResourceResolver adminResolver = renderContext.getScriptResourceResolver();
+ Resource caller = adminResolver.getResource(callerPath);
+ AsyncContainer asyncContainer = environment.run(caller, identifier, globalBindings, arguments);
+ return ProviderOutcome.success(jsValueAdapter.adapt(asyncContainer));
+ } finally {
+ if (environment != null) {
+ environment.cleanup();
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java
new file mode 100644
index 0000000..7d22159
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Utils.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.sightly.js.impl;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+import java.util.Collections;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+
+/**
+ * Utilities for script evaluation
+ */
+public class Utils {
+ private static final String EXTENSION = "js";
+
+ public static final Bindings EMPTY_BINDINGS = new SimpleBindings(Collections.<String, Object>emptyMap());
+
+ public static SlingScriptHelper getHelper(Bindings bindings) {
+ return (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+ }
+
+ public static boolean isJsScript(String identifier) {
+ String extension = StringUtils.substringAfterLast(identifier, ".");
+ return EXTENSION.equalsIgnoreCase(extension);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java
new file mode 100644
index 0000000..176e85f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/Variables.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.sightly.js.impl;
+
+/**
+ * Variables exposed to js scripts
+ */
+public final class Variables {
+
+ public static final String CONSOLE = "console";
+
+ public static final String JS_USE = "use";
+ public static final String SET_TIMEOUT = "setTimeout";
+ public static final String SET_IMMEDIATE = "setImmediate";
+
+ public static final String MODULE = "module";
+ public static final String EXPORTS = "exports";
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java
new file mode 100644
index 0000000..b9bbd5c
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncContainer.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.async;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple container for asynchronously provided values
+ */
+public class AsyncContainer {
+
+ private Object value;
+ private boolean completed;
+ private List<UnaryCallback> callbacks = new ArrayList<UnaryCallback>();
+
+ /**
+ * Add a listener that will receive the value in this container when it will
+ * be filled. If the container already has a value, the callback is called
+ * immediately.
+ * @param unaryCallback the callback that will receive the result
+ */
+ public void addListener(UnaryCallback unaryCallback) {
+ callbacks.add(unaryCallback);
+ if (completed) {
+ notifyListener(unaryCallback);
+ }
+ }
+
+ /**
+ * Get the result of this holder
+ * @return the holder result
+ * @throws java.util.NoSuchElementException if the result has not yet been set
+ */
+ public Object getResult() {
+ return value;
+ }
+
+ /**
+ * Check whether the container was completed with a value
+ * @return the completion status
+ */
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ /**
+ * Complete this async container with a value
+ * @param value the result value
+ * @throws java.lang.IllegalStateException if the container has been previously filled
+ */
+ public void complete(Object value) {
+ if (completed) {
+ throw new IllegalStateException("Value was already completed");
+ }
+ completed = true;
+ this.value = value;
+ for (UnaryCallback callback : callbacks) {
+ notifyListener(callback);
+ }
+ }
+
+ /**
+ * Create a callback that will complete this container
+ * @return the completion callback
+ */
+ public UnaryCallback createCompletionCallback() {
+ return new UnaryCallback() {
+ @Override
+ public void invoke(Object arg) {
+ complete(arg);
+ }
+ };
+ }
+
+ private void notifyListener(UnaryCallback unaryCallback) {
+ unaryCallback.invoke(value);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java
new file mode 100644
index 0000000..098bd71
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/AsyncExtractor.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.async;
+
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+
+/**
+ *
+ */
+public class AsyncExtractor {
+
+ public static final String THEN_METHOD = "then";
+
+ public void extract(Object jsObj, UnaryCallback unaryCallback) {
+ if (!isPromise(jsObj)) {
+ unaryCallback.invoke(jsObj);
+ }
+ if (jsObj instanceof AsyncContainer) {
+ ((AsyncContainer) jsObj).addListener(unaryCallback);
+ }
+ if (jsObj instanceof ScriptableObject) {
+ ScriptableObject scriptableObject = (ScriptableObject) jsObj;
+ decodeJSPromise(scriptableObject, unaryCallback);
+ }
+ }
+
+ private void decodeJSPromise(final Scriptable promise, final UnaryCallback callback) {
+ try {
+ Context context = Context.enter();
+ final AsyncContainer errorContainer = new AsyncContainer();
+ final Function errorHandler = createErrorHandler(errorContainer);
+ final Function successHandler = convertCallback(callback);
+ EventLoopInterop.schedule(context, new Runnable() {
+ @Override
+ public void run() {
+ ScriptableObject.callMethod(promise, THEN_METHOD,
+ new Object[] {successHandler, errorHandler});
+ }
+ });
+ if (errorContainer.isCompleted()) {
+ throw new SightlyException("Promise has completed with failure: " + Context.toString(errorContainer.getResult()));
+ }
+ } finally {
+ Context.exit();
+ }
+ }
+
+ private Function createErrorHandler(AsyncContainer asyncContainer) {
+ return convertCallback(asyncContainer.createCompletionCallback());
+ }
+
+ public boolean isPromise(Object jsObj) {
+ if (jsObj instanceof AsyncContainer) {
+ return true;
+ }
+ if (jsObj instanceof ScriptableObject) {
+ Scriptable scriptable = (Scriptable) jsObj;
+ return ScriptableObject.hasProperty(scriptable, THEN_METHOD);
+ }
+ return false;
+ }
+
+ private static Function convertCallback(final UnaryCallback unaryCallback) {
+ return new BaseFunction() {
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ Object arg = (args.length == 0) ? Context.getUndefinedValue() : args[0];
+ unaryCallback.invoke(arg);
+ return Context.getUndefinedValue();
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java
new file mode 100644
index 0000000..623de69
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingBindingsValuesProvider.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.async;
+
+import javax.script.Bindings;
+
+import org.apache.sling.scripting.api.BindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.Variables;
+
+/**
+ * Value provider for timing functions
+ */
+public final class TimingBindingsValuesProvider implements BindingsValuesProvider {
+
+ public static final TimingBindingsValuesProvider INSTANCE = new TimingBindingsValuesProvider();
+
+ private TimingBindingsValuesProvider() {
+ }
+
+ @Override
+ public void addBindings(Bindings bindings) {
+ bindings.put(Variables.SET_TIMEOUT, TimingFunction.INSTANCE);
+ bindings.put(Variables.SET_IMMEDIATE, TimingFunction.INSTANCE);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java
new file mode 100644
index 0000000..5cf43b7
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/TimingFunction.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.async;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsUtils;
+
+/**
+ * Timing function for JS scripts that use async constructs
+ */
+public final class TimingFunction extends BaseFunction {
+
+ public static final TimingFunction INSTANCE = new TimingFunction();
+
+ private TimingFunction() {
+ }
+
+ @Override
+ public Object call(final Context cx, final Scriptable scope, Scriptable thisObj, Object[] args) {
+ if (args.length == 0) {
+ return Context.getUndefinedValue();
+ }
+ if (!(args[0] instanceof Function)) {
+ throw new IllegalArgumentException("Timing function must receive a function as the first argument");
+ }
+ final Function function = (Function) args[0];
+ return EventLoopInterop.schedule(cx, new Runnable() {
+ @Override
+ public void run() {
+ JsUtils.callFn(function, cx, scope, null, new Object[0]);
+ }
+ });
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java
new file mode 100644
index 0000000..0ec0e61
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/async/UnaryCallback.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.async;
+
+/**
+ * Unary callback function
+ */
+public interface UnaryCallback {
+
+ /**
+ * Call the callback with one argument
+ * @param arg the callback argument
+ */
+ void invoke(Object arg);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java
new file mode 100644
index 0000000..9b1c675
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/CommonJsModule.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.cjs;
+
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+/**
+ * CommonJS module implementation
+ */
+public class CommonJsModule extends ScriptableObject {
+
+ private static final String EXPORTS = "exports";
+
+ private Object exports = new ExportsObject();
+ private boolean modifiedModule;
+
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ if (name.equals(EXPORTS)) {
+ return exports;
+ }
+ return super.get(name, start);
+ }
+
+ @Override
+ public void put(String name, Scriptable start, Object value) {
+ if (name.equals(EXPORTS)) {
+ setExports(value);
+ } else {
+ super.put(name, start, value);
+ }
+ }
+
+ public Object getExports() {
+ return exports;
+ }
+
+ public void setExports(Object exports) {
+ modifiedModule = true;
+ this.exports = exports;
+ }
+
+ public boolean isModified() {
+ return modifiedModule || ((ExportsObject) exports).isModified();
+ }
+
+ @Override
+ public String getClassName() {
+ return "Module";
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java
new file mode 100644
index 0000000..3822861
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/cjs/ExportsObject.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.cjs;
+
+
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+/**
+ * The default exports object
+ */
+public class ExportsObject extends ScriptableObject {
+
+ private boolean modified;
+
+ @Override
+ public void put(String name, Scriptable start, Object value) {
+ modified = true;
+ super.put(name, start, value);
+ }
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ @Override
+ public String getClassName() {
+ return "exports";
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
new file mode 100644
index 0000000..6731ed4
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.jsapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.SimpleBindings;
+import javax.servlet.http.HttpServletRequest;
+
+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.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.scripting.sightly.js.impl.JsEnvironment;
+import org.apache.sling.scripting.sightly.js.impl.Variables;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingBindingsValuesProvider;
+import org.apache.sling.scripting.sightly.js.impl.async.TimingFunction;
+import org.apache.sling.scripting.sightly.js.impl.cjs.CommonJsModule;
+import org.apache.sling.scripting.sightly.js.impl.rhino.HybridObject;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsValueAdapter;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Script;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides the {@code sightly} namespace for usage in Sightly & JS scripts
+ * called from Sightly
+ */
+@Component(metatype = true, label = "Apache Sling Scripting Sightly JavaScript Bindings Provider",
+ description = "The Apache Sling Scripting Sightly JavaScript Bindings Provider loads the JS Use-API and makes it available in the" +
+ " bindings map.")
+@Service(SlyBindingsValuesProvider.class)
+@Properties({
+ @Property(
+ name = SlyBindingsValuesProvider.SCR_PROP_JS_BINDING_IMPLEMENTATIONS,
+ value = {
+ "sightly:" + SlyBindingsValuesProvider.SLING_NS_PATH
+ },
+ unbounded = PropertyUnbounded.ARRAY,
+ label = "Script Factories",
+ description = "Script factories to load in the bindings map. The entries should be in the form " +
+ "'namespace:/path/from/repository'."
+ )
+})
+@SuppressWarnings("unused")
+public class SlyBindingsValuesProvider {
+
+ public static final String SCR_PROP_JS_BINDING_IMPLEMENTATIONS = "org.apache.sling.scripting.sightly.js.bindings";
+
+ public static final String SLING_NS_PATH = "/libs/sling/sightly/js/internal/sly.js";
+ public static final String Q_PATH = "/libs/sling/sightly/js/3rd-party/q.js";
+
+ private static final String REQ_NS = SlyBindingsValuesProvider.class.getCanonicalName();
+
+ private static final Logger log = LoggerFactory.getLogger(SlyBindingsValuesProvider.class);
+
+ @Reference
+ private ScriptEngineManager scriptEngineManager;
+
+ @Reference
+ private ResourceResolverFactory rrf = null;
+
+ private final AsyncExtractor asyncExtractor = new AsyncExtractor();
+ private final JsValueAdapter jsValueAdapter = new JsValueAdapter(asyncExtractor);
+
+ private Map<String, String> scriptPaths = new HashMap<String, String>();
+ private Map<String, Function> factories = new HashMap<String, Function>();
+
+ private Script qScript;
+ private final ScriptableObject qScope = createQScope();
+
+ public void processBindings(Bindings bindings) {
+ if (needsInit()) {
+ init(bindings);
+ }
+ Context context = null;
+ try {
+ context = Context.enter();
+ Object qInstance = obtainQInstance(context, bindings);
+ if (qInstance == null) {
+ return;
+ }
+ for (Map.Entry<String, Function> entry : factories.entrySet()) {
+ addBinding(context, entry.getValue(), bindings, entry.getKey(), qInstance);
+ }
+ } finally {
+ if (context != null) {
+ Context.exit();
+ }
+ }
+ }
+
+ @Activate
+ protected void activate(ComponentContext componentContext) {
+ Dictionary properties = componentContext.getProperties();
+ String[] factories = PropertiesUtil.toStringArray(properties.get(SCR_PROP_JS_BINDING_IMPLEMENTATIONS), new String[]{SLING_NS_PATH});
+ scriptPaths = new HashMap<String, String>(factories.length);
+ for (String f : factories) {
+ String[] parts = f.split(":");
+ if (parts.length == 2) {
+ scriptPaths.put(parts[0], parts[1]);
+ }
+ }
+ }
+
+ @Deactivate
+ protected void deactivate(ComponentContext componentContext) {
+ if (scriptPaths != null) {
+ scriptPaths.clear();
+ }
+ if (factories != null) {
+ factories.clear();
+ }
+ }
+
+
+ private void addBinding(Context context, Function factory, Bindings bindings, String globalName, Object qInstance) {
+ if (factory == null) {
+ return;
+ }
+ Object result = factory.call(context, factory, factory, new Object[] {bindings, qInstance});
+ HybridObject global = new HybridObject((Scriptable) result, jsValueAdapter);
+ bindings.put(globalName, global);
+ }
+
+ private boolean needsInit() {
+ return factories == null || factories.isEmpty() || qScript == null;
+ }
+
+ private synchronized void init(Bindings bindings) {
+ if (needsInit()) {
+ ensureFactoriesLoaded(bindings);
+ }
+ }
+
+ private void ensureFactoriesLoaded(Bindings bindings) {
+ JsEnvironment jsEnvironment = null;
+ try {
+ ScriptEngine scriptEngine = obtainEngine();
+ if (scriptEngine == null) {
+ return;
+ }
+ jsEnvironment = new JsEnvironment(scriptEngine);
+ jsEnvironment.initialize();
+ factories = new HashMap<String, Function>(scriptPaths.size());
+ for (Map.Entry<String, String> entry : scriptPaths.entrySet()) {
+ factories.put(entry.getKey(), loadFactory(jsEnvironment, entry.getValue(), bindings));
+ }
+ qScript = loadQScript();
+ } finally {
+ if (jsEnvironment != null) {
+ jsEnvironment.cleanup();
+ }
+ }
+ }
+
+ private Function loadFactory(JsEnvironment jsEnvironment, String path, Bindings bindings) {
+ ResourceResolver resolver = null;
+ try {
+ resolver = rrf.getAdministrativeResourceResolver(null);
+ Resource resource = resolver.getResource(path);
+ if (resource == null) {
+ log.warn("Sly namespace loader could not find the following script: " + path);
+ return null;
+ }
+ AsyncContainer container = jsEnvironment.runResource(resource, createBindings(bindings), new SimpleBindings());
+ Object obj = container.getResult();
+ if (!(obj instanceof Function)) {
+ log.warn("Script was expected to return a function");
+ return null;
+ }
+ return (Function) obj;
+ } catch (LoginException e) {
+ log.error("Cannot evaluate script " + path, e);
+ return null;
+ } finally {
+ if (resolver != null) {
+ resolver.close();
+ }
+ }
+ }
+
+ private Bindings createBindings(Bindings global) {
+ Bindings bindings = new SimpleBindings();
+ bindings.putAll(global);
+ TimingBindingsValuesProvider.INSTANCE.addBindings(bindings);
+ return bindings;
+ }
+
+ private ScriptEngine obtainEngine() {
+ return scriptEngineManager.getEngineByName("javascript");
+ }
+
+ private Object obtainQInstance(Context context, Bindings bindings) {
+ if (qScript == null) {
+ return null;
+ }
+ HttpServletRequest request = (HttpServletRequest) bindings.get(SlingBindings.REQUEST);
+ Object qInstance = null;
+ if (request != null) {
+ qInstance = request.getAttribute(REQ_NS);
+ }
+ if (qInstance == null) {
+ qInstance = createQInstance(context, qScript);
+ if (request != null) {
+ request.setAttribute(REQ_NS, qInstance);
+ }
+ }
+ return qInstance;
+ }
+
+ private ScriptableObject createQScope() {
+ Context context = Context.enter();
+ try {
+ ScriptableObject scope = context.initStandardObjects();
+ ScriptableObject.putProperty(scope, Variables.SET_IMMEDIATE, TimingFunction.INSTANCE);
+ ScriptableObject.putProperty(scope, Variables.SET_TIMEOUT, TimingFunction.INSTANCE);
+ return scope;
+ } finally {
+ Context.exit();
+ }
+ }
+
+ private Object createQInstance(Context context, Script qScript) {
+ CommonJsModule module = new CommonJsModule();
+ Scriptable tempScope = context.newObject(qScope);
+ ScriptableObject.putProperty(tempScope, Variables.MODULE, module);
+ ScriptableObject.putProperty(tempScope, Variables.EXPORTS, module.getExports());
+ qScript.exec(context, tempScope);
+ return module.getExports();
+ }
+
+ private Script loadQScript() {
+ ResourceResolver resourceResolver = null;
+ Context context = Context.enter();
+ context.initStandardObjects();
+ context.setOptimizationLevel(9);
+ InputStream reader = null;
+ try {
+ resourceResolver = rrf.getAdministrativeResourceResolver(null);
+ Resource resource = resourceResolver.getResource(Q_PATH);
+ if (resource == null) {
+ log.warn("Could not load Q library at path: " + Q_PATH);
+ return null;
+ }
+ reader = resource.adaptTo(InputStream.class);
+ if (reader == null) {
+ log.warn("Could not read content of Q library");
+ return null;
+ }
+ return context.compileReader(new InputStreamReader(reader), Q_PATH, 0, null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ Context.exit();
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ log.error("Error while closing reader", e);
+ }
+ }
+ if (resourceResolver != null) {
+ resourceResolver.close();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java
new file mode 100644
index 0000000..ed565ef
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoop.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.loop;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Simulates an event loop for the Rhino JS engine.
+ */
+public class EventLoop {
+
+ private static final Logger log = LoggerFactory.getLogger(EventLoop.class);
+
+ private Queue<Task> taskQueue = new LinkedList<Task>();
+ private boolean isRunning;
+
+ /**
+ * Add a task to the queue. If the queue is empty, start running tasks. If it
+ * isn't empty, continue running the available tasks
+ * @param task the task to be added
+ */
+ public void schedule(Task task) {
+ taskQueue.offer(task);
+ run();
+ }
+
+ private void run() {
+ if (isRunning) {
+ return;
+ }
+ isRunning = true;
+ try {
+ // Holds the first exception encountered. If there is such a first exception, it will be
+ // rethrown
+ Exception thrownException = null;
+ while (!taskQueue.isEmpty()) {
+ Task task = taskQueue.poll();
+ try {
+ task.run();
+ } catch (Exception e) {
+ if (thrownException == null) {
+ thrownException = e; //first exception
+ } else {
+ log.error("Additional error occurred while running JS script: ", e);
+ }
+ }
+ }
+ if (thrownException != null) {
+ throw new SightlyException(thrownException);
+ }
+ } finally {
+ isRunning = false;
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java
new file mode 100644
index 0000000..af9b8d5
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/EventLoopInterop.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.loop;
+
+import org.mozilla.javascript.Context;
+
+/**
+ * Event-loop utilities for interoperability with JS code
+ */
+public class EventLoopInterop {
+
+ public static final String EVENT_LOOP_KEY = "EventLoop";
+
+ public static EventLoop obtainEventLoop(Context context) {
+ EventLoop eventLoop = getEventLoop(context);
+ if (eventLoop == null) {
+ eventLoop = new EventLoop();
+ context.putThreadLocal(EVENT_LOOP_KEY, eventLoop);
+ }
+ return eventLoop;
+ }
+
+ public static void cleanupEventLoop(Context context) {
+ context.removeThreadLocal(EVENT_LOOP_KEY);
+ }
+
+ public static Task schedule(Context context, Runnable runnable) {
+ Task task = new Task(runnable);
+ obtainEventLoop(context).schedule(task);
+ return task;
+ }
+
+ private static EventLoop getEventLoop(Context context) {
+ return (EventLoop) context.getThreadLocal(EVENT_LOOP_KEY);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java
new file mode 100644
index 0000000..1a96a12
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/loop/Task.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.loop;
+
+/**
+ * Task in an event loop
+ */
+public class Task {
+
+ private final Runnable runnable;
+ private boolean active;
+
+ public Task(Runnable runnable) {
+ this.runnable = runnable;
+ this.active = true;
+ }
+
+ public void run() {
+ if (active) {
+ runnable.run();
+ }
+ }
+
+ public void deactivate() {
+ this.active = false;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java
new file mode 100644
index 0000000..2d52120
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/HybridObject.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.rhino;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sling.scripting.sightly.Record;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+
+/**
+ * Instances of this class can be used in both Sightly & JS scripts
+ */
+public class HybridObject implements Scriptable, Record<Object> {
+
+ private final Scriptable scriptable;
+ private final JsValueAdapter jsValueAdapter;
+
+ public HybridObject(Scriptable scriptable, JsValueAdapter jsValueAdapter) {
+ this.scriptable = scriptable;
+ this.jsValueAdapter = jsValueAdapter;
+ }
+
+ // Record implementation
+
+ @Override
+ public Object getProperty(String name) {
+ if (name == null) {
+ return null;
+ }
+ Context.enter();
+ try {
+ return getAdapted(name);
+ } finally {
+ Context.exit();
+ }
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ Object[] properties = scriptable.getIds();
+ Set<String> keys = new HashSet<String>();
+ for (Object property: properties) {
+ if (property instanceof String) {
+ keys.add((String) property);
+ }
+ }
+ return keys;
+ }
+
+ private Object getAdapted(String key) {
+ Object obj = ScriptableObject.getProperty(scriptable, key);
+ if (obj == null) {
+ return null;
+ }
+ if (obj instanceof Function) {
+ return jsValueAdapter.adapt(JsUtils.callFn((Function) obj, null, scriptable, scriptable, new Object[0]));
+ }
+ return jsValueAdapter.adapt(obj);
+ }
+
+ // Scriptable implementation
+
+ @Override
+ public String getClassName() {
+ return scriptable.getClassName();
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ return scriptable.get(name, start);
+ }
+
+ @Override
+ public Object get(int index, Scriptable start) {
+ return scriptable.get(index, start);
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start) {
+ return scriptable.has(name, start);
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start) {
+ return scriptable.has(index, start);
+ }
+
+ @Override
+ public void put(String name, Scriptable start, Object value) {
+ scriptable.put(name, start, value);
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value) {
+ scriptable.put(index, start, value);
+ }
+
+ @Override
+ public void delete(String name) {
+ scriptable.delete(name);
+ }
+
+ @Override
+ public void delete(int index) {
+ scriptable.delete(index);
+ }
+
+ @Override
+ public Scriptable getPrototype() {
+ return scriptable.getPrototype();
+ }
+
+ @Override
+ public void setPrototype(Scriptable prototype) {
+ scriptable.setPrototype(prototype);
+ }
+
+ @Override
+ public Scriptable getParentScope() {
+ return scriptable.getParentScope();
+ }
+
+ @Override
+ public void setParentScope(Scriptable parent) {
+ scriptable.setParentScope(parent);
+ }
+
+ @Override
+ public Object[] getIds() {
+ return scriptable.getIds();
+ }
+
+ @Override
+ public Object getDefaultValue(Class hint) {
+ return scriptable.getDefaultValue(hint);
+ }
+
+ @Override
+ public boolean hasInstance(Scriptable instance) {
+ return scriptable.hasInstance(instance);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java
new file mode 100644
index 0000000..cfc6cad
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsUtils.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.rhino;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+
+/**
+ * Utilities when inter-operating with JS scripts
+ */
+public class JsUtils {
+
+ public static Object callFn(Function function, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ boolean exitContext = false;
+ if (Context.getCurrentContext() == null) {
+ Context.enter();
+ exitContext = true;
+ }
+ Context context = (cx == null) ? Context.getCurrentContext() : cx;
+ Object result = function.call(context, scope, thisObj, args);
+ if (exitContext) {
+ Context.exit();
+ }
+ return result;
+ }
+
+
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java
new file mode 100644
index 0000000..81226c6
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/rhino/JsValueAdapter.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.rhino;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Wrapper;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor;
+
+/**
+ * Converts JS objects to Java objects
+ */
+public class JsValueAdapter {
+
+ private static final Map<String, Class<?>> knownConversions = new HashMap<String, Class<?>>();
+
+ static {
+ knownConversions.put("String", String.class);
+ knownConversions.put("Date", Date.class);
+ }
+
+ private final AsyncExtractor asyncExtractor;
+
+ public JsValueAdapter(AsyncExtractor asyncExtractor) {
+ this.asyncExtractor = asyncExtractor;
+ }
+
+ /**
+ * Convert a given JS value to a Java object
+ * @param jsValue the original JS value
+ * @return the Java correspondent
+ */
+ @SuppressWarnings("unchecked")
+ public Object adapt(Object jsValue) {
+ if (jsValue == null || jsValue == Context.getUndefinedValue() || jsValue == ScriptableObject.NOT_FOUND) {
+ return null;
+ }
+ if (jsValue instanceof Wrapper) {
+ return adapt(((Wrapper) jsValue).unwrap());
+ }
+ if (asyncExtractor.isPromise(jsValue)) {
+ return adapt(forceAsync(jsValue));
+ }
+ if (jsValue instanceof ScriptableObject) {
+ return extractScriptable((ScriptableObject) jsValue);
+ }
+ if (jsValue instanceof CharSequence) {
+ //convert any string-like type to plain java strings
+ return jsValue.toString();
+ }
+ if (jsValue instanceof Map) {
+ return convertMap((Map) jsValue);
+ }
+ if (jsValue instanceof Iterable) {
+ return convertIterable((Iterable) jsValue);
+ }
+ if (jsValue instanceof Number) {
+ return convertNumber((Number) jsValue);
+ }
+ if (jsValue instanceof Object[]) {
+ return convertIterable(Arrays.asList((Object[]) jsValue));
+ }
+ return jsValue;
+ }
+
+ private Object convertNumber(Number numValue) {
+ if (numValue instanceof Double) {
+ if (isLong((Double) numValue)) {
+ return numValue.longValue();
+ }
+ }
+ if (numValue instanceof Float) {
+ if (isLong((Float) numValue)) {
+ return numValue.longValue();
+ }
+ }
+ return numValue;
+ }
+
+ private boolean isLong(double x) {
+ return x == Math.floor(x);
+ }
+
+ private Object forceAsync(Object jsValue) {
+ AsyncContainer asyncContainer = new AsyncContainer();
+ asyncExtractor.extract(jsValue, asyncContainer.createCompletionCallback());
+ return asyncContainer.getResult();
+ }
+
+ private Object extractScriptable(ScriptableObject scriptableObject) {
+ Object obj = tryKnownConversion(scriptableObject);
+ if (obj != null) { return obj; }
+ if (scriptableObject instanceof NativeArray) {
+ return convertNativeArray((NativeArray) scriptableObject);
+ }
+ if (scriptableObject instanceof Function) {
+ return callFunction((Function) scriptableObject);
+ }
+ return new HybridObject(scriptableObject, this);
+ }
+
+ private Object callFunction(Function function) {
+ Object result = JsUtils.callFn(function, null, function, function, new Object[0]);
+ return adapt(result);
+ }
+
+ private Object[] convertNativeArray(NativeArray nativeArray) {
+ int length = (int) nativeArray.getLength();
+ Object[] objects = new Object[length];
+ for (int i = 0; i < length; i++) {
+ Object jsItem = nativeArray.get(i, nativeArray);
+ objects[i] = adapt(jsItem);
+ }
+ return objects;
+ }
+
+ private Map<Object, Object> convertMap(Map<Object, Object> original) {
+ Map<Object, Object> map = new HashMap<Object, Object>();
+ for (Map.Entry<Object, Object> entry : original.entrySet()) {
+ map.put(entry.getKey(), adapt(entry.getValue()));
+ }
+ return map;
+ }
+
+ private List<Object> convertIterable(Iterable<Object> iterable) {
+ List<Object> objects = new ArrayList<Object>();
+ for (Object obj : iterable) {
+ objects.add(adapt(obj));
+ }
+ return objects;
+ }
+
+ private static Object tryKnownConversion(ScriptableObject object) {
+ String className = object.getClassName();
+ Class<?> cls = knownConversions.get(className);
+ return (cls == null) ? null : Context.jsToJava(object, cls);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
new file mode 100644
index 0000000..ef24105
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.use;
+
+import javax.script.Bindings;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.js.impl.JsEnvironment;
+import org.apache.sling.scripting.sightly.js.impl.Utils;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+
+/**
+ * Resolves dependencies specified by the Use function
+ */
+public class DependencyResolver {
+
+ private final Resource caller;
+ private final JsEnvironment jsEnvironment;
+ private final Bindings globalBindings;
+
+ public DependencyResolver(Resource resource, JsEnvironment jsEnvironment, Bindings globalBindings) {
+ this.caller = resource;
+ this.jsEnvironment = jsEnvironment;
+ this.globalBindings = globalBindings;
+ }
+
+ /**
+ * Resolve a dependency
+ * @param dependency the dependency identifier
+ * @param callback the callback that will receive the resolved dependency
+ */
+ public void resolve(String dependency, UnaryCallback callback) {
+ if (!Utils.isJsScript(dependency)) {
+ throw new SightlyException("Only JS scripts are allowed as dependencies. Invalid dependency: " + dependency);
+ }
+ jsEnvironment.run(caller, dependency, globalBindings, Utils.EMPTY_BINDINGS, callback);
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java
new file mode 100644
index 0000000..fbfe132
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/UseFunction.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.sightly.js.impl.use;
+
+import javax.script.Bindings;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.apache.sling.scripting.sightly.js.impl.async.UnaryCallback;
+import org.apache.sling.scripting.sightly.js.impl.loop.EventLoopInterop;
+import org.apache.sling.scripting.sightly.js.impl.rhino.JsUtils;
+
+/**
+ * The JavaScript {@code use} function
+ */
+public class UseFunction extends BaseFunction {
+
+ private final DependencyResolver dependencyResolver;
+ private final Scriptable thisObj;
+
+ public UseFunction(DependencyResolver dependencyResolver, Bindings arguments) {
+ this.dependencyResolver = dependencyResolver;
+ this.thisObj = createThisBinding(arguments);
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ Function function;
+ List<String> depNames;
+ if (args.length == 0) {
+ throw new IllegalArgumentException("Not enough arguments for use");
+ } else if (args.length == 1) {
+ function = decodeCallback(args[0]);
+ depNames = Collections.emptyList();
+ } else {
+ function = decodeCallback(args[1]);
+ depNames = decodeDepNames(args[0]);
+ }
+ return use(depNames, function, cx, scope);
+ }
+
+ private Object use(List<String> depNames, final Function callback, final Context cx, final Scriptable scope) {
+ final AsyncContainer asyncContainer = new AsyncContainer();
+ if (depNames.isEmpty()) {
+ callImmediate(callback, asyncContainer, cx, scope);
+ } else {
+ final int[] counter = {depNames.size()};
+ final Object[] dependencies = new Object[depNames.size()];
+ for (int i = 0; i < depNames.size(); i++) {
+ final int dependencyPos = i;
+ dependencyResolver.resolve(depNames.get(i), new UnaryCallback() {
+ @Override
+ public void invoke(Object arg) {
+ counter[0]--;
+ dependencies[dependencyPos] = arg;
+ if (counter[0] == 0) {
+ Object result = JsUtils.callFn(callback, cx, scope, thisObj, dependencies);
+ asyncContainer.complete(result);
+ }
+ }
+ });
+ }
+ }
+ return asyncContainer;
+ }
+
+ private void callImmediate(final Function callback, final AsyncContainer asyncContainer, final Context cx, final Scriptable scope) {
+ EventLoopInterop.schedule(cx, new Runnable() {
+ @Override
+ public void run() {
+ Object value = JsUtils.callFn(callback, cx, scope, thisObj, new Object[0]);
+ asyncContainer.complete(value);
+ }
+ });
+ }
+
+ private Function decodeCallback(Object obj) {
+ if (!(obj instanceof Function)) {
+ throw new IllegalArgumentException("No callback argument supplied");
+ }
+ return (Function) obj;
+ }
+
+ private List<String> decodeDepNames(Object obj) {
+ if (obj instanceof NativeArray) {
+ return decodeDepArray((NativeArray) obj);
+ }
+ return Collections.singletonList(jsToString(obj));
+ }
+
+ private List<String> decodeDepArray(NativeArray nativeArray) {
+ int depLength = (int) nativeArray.getLength();
+ List<String> depNames = new ArrayList<String>(depLength);
+ for (int i = 0; i < depLength; i++) {
+ String depName = jsToString(nativeArray.get(i, nativeArray));
+ depNames.add(depName);
+ }
+ return depNames;
+ }
+
+ private String jsToString(Object obj) {
+ return (String) Context.jsToJava(obj, String.class);
+ }
+
+ private Scriptable createThisBinding(Bindings arguments) {
+ NativeObject nativeObject = new NativeObject();
+ for (Map.Entry<String, Object> entry : arguments.entrySet()) {
+ ScriptableObject.putProperty(nativeObject, entry.getKey(), entry.getValue());
+ }
+ return nativeObject;
+ }
+}
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js
new file mode 100644
index 0000000..5ed1dba
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/3rd-party/q.js
@@ -0,0 +1,1935 @@
+// vim:ts=4:sts=4:sw=4:
+/*!
+ *
+ * Copyright 2009-2012 Kris Kowal under the terms of the MIT
+ * license found at http://github.com/kriskowal/q/raw/master/LICENSE
+ *
+ * With parts by Tyler Close
+ * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
+ * at http://www.opensource.org/licenses/mit-license.html
+ * Forked at ref_send.js version: 2009-05-11
+ *
+ * With parts by Mark Miller
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed 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.
+ *
+ */
+
+(function (definition) {
+ // Turn off strict mode for this function so we can assign to global.Q
+ /* jshint strict: false */
+
+ // This file will function properly as a <script> tag, or a module
+ // using CommonJS and NodeJS or RequireJS module formats. In
+ // Common/Node/RequireJS, the module exports the Q API and when
+ // executed as a simple <script>, it creates a Q global instead.
+
+ // Montage Require
+ if (typeof bootstrap === "function") {
+ bootstrap("promise", definition);
+
+ // CommonJS
+ } else if (typeof exports === "object") {
+ module.exports = definition();
+
+ // RequireJS
+ } else if (typeof define === "function" && define.amd) {
+ define(definition);
+
+ // SES (Secure EcmaScript)
+ } else if (typeof ses !== "undefined") {
+ if (!ses.ok()) {
+ return;
+ } else {
+ ses.makeQ = definition;
+ }
+
+ // <script>
+ } else {
+ Q = definition();
+ }
+
+})(function () {
+"use strict";
+
+var hasStacks = false;
+try {
+ throw new Error();
+} catch (e) {
+ hasStacks = !!e.stack;
+}
+
+// All code after this point will be filtered from stack traces reported
+// by Q.
+var qStartingLine = captureLine();
+var qFileName;
+
+// shims
+
+// used for fallback in "allResolved"
+var noop = function () {};
+
+// Use the fastest possible means to execute a task in a future turn
+// of the event loop.
+var nextTick =(function () {
+ // linked list of tasks (single, with head node)
+ var head = {task: void 0, next: null};
+ var tail = head;
+ var flushing = false;
+ var requestTick = void 0;
+ var isNodeJS = false;
+
+ function flush() {
+ /* jshint loopfunc: true */
+
+ while (head.next) {
+ head = head.next;
+ var task = head.task;
+ head.task = void 0;
+ var domain = head.domain;
+
+ if (domain) {
+ head.domain = void 0;
+ domain.enter();
+ }
+
+ try {
+ task();
+
+ } catch (e) {
+ if (isNodeJS) {
+ // In node, uncaught exceptions are considered fatal errors.
+ // Re-throw them synchronously to interrupt flushing!
+
+ // Ensure continuation if the uncaught exception is suppressed
+ // listening "uncaughtException" events (as domains does).
+ // Continue in next event to avoid tick recursion.
+ if (domain) {
+ domain.exit();
+ }
+ setTimeout(flush, 0);
+ if (domain) {
+ domain.enter();
+ }
+
+ throw e;
+
+ } else {
+ // In browsers, uncaught exceptions are not fatal.
+ // Re-throw them asynchronously to avoid slow-downs.
+ setTimeout(function() {
+ throw e;
+ }, 0);
+ }
+ }
+
+ if (domain) {
+ domain.exit();
+ }
+ }
+
+ flushing = false;
+ }
+
+ nextTick = function (task) {
+ tail = tail.next = {
+ task: task,
+ domain: isNodeJS && process.domain,
+ next: null
+ };
+
+ if (!flushing) {
+ flushing = true;
+ requestTick();
+ }
+ };
+
+ if (typeof process !== "undefined" && process.nextTick) {
+ // Node.js before 0.9. Note that some fake-Node environments, like the
+ // Mocha test runner, introduce a `process` global without a `nextTick`.
+ isNodeJS = true;
+
+ requestTick = function () {
+ process.nextTick(flush);
+ };
+
+ } else if (typeof setImmediate === "function") {
+ // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
+ if (typeof window !== "undefined") {
+ requestTick = setImmediate.bind(window, flush);
+ } else {
+ requestTick = function () {
+ setImmediate(flush);
+ };
+ }
+
+ } else if (typeof MessageChannel !== "undefined") {
+ // modern browsers
+ // http://www.nonblocking.io/2011/06/windownexttick.html
+ var channel = new MessageChannel();
+ // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create
+ // working message ports the first time a page loads.
+ channel.port1.onmessage = function () {
+ requestTick = requestPortTick;
+ channel.port1.onmessage = flush;
+ flush();
+ };
+ var requestPortTick = function () {
+ // Opera requires us to provide a message payload, regardless of
+ // whether we use it.
+ channel.port2.postMessage(0);
+ };
+ requestTick = function () {
+ setTimeout(flush, 0);
+ requestPortTick();
+ };
+
+ } else {
+ // old browsers
+ requestTick = function () {
+ setTimeout(flush, 0);
+ };
+ }
+
+ return nextTick;
+})();
+
+// Attempt to make generics safe in the face of downstream
+// modifications.
+// There is no situation where this is necessary.
+// If you need a security guarantee, these primordials need to be
+// deeply frozen anyway, and if you don’t need a security guarantee,
+// this is just plain paranoid.
+// However, this **might** have the nice side-effect of reducing the size of
+// the minified code by reducing x.call() to merely x()
+// See Mark Miller’s explanation of what this does.
+// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
+var call = Function.call;
+function uncurryThis(f) {
+ return function () {
+ return call.apply(f, arguments);
+ };
+}
+// This is equivalent, but slower:
+// uncurryThis = Function_bind.bind(Function_bind.call);
+// http://jsperf.com/uncurrythis
+
+var array_slice = uncurryThis(Array.prototype.slice);
+
+var array_reduce = uncurryThis(
+ Array.prototype.reduce || function (callback, basis) {
+ var index = 0,
+ length = this.length;
+ // concerning the initial value, if one is not provided
+ if (arguments.length === 1) {
+ // seek to the first value in the array, accounting
+ // for the possibility that is is a sparse array
+ do {
+ if (index in this) {
+ basis = this[index++];
+ break;
+ }
+ if (++index >= length) {
+ throw new TypeError();
+ }
+ } while (1);
+ }
+ // reduce
+ for (; index < length; index++) {
+ // account for the possibility that the array is sparse
+ if (index in this) {
+ basis = callback(basis, this[index], index);
+ }
+ }
+ return basis;
+ }
+);
+
+var array_indexOf = uncurryThis(
+ Array.prototype.indexOf || function (value) {
+ // not a very good shim, but good enough for our one use of it
+ for (var i = 0; i < this.length; i++) {
+ if (this[i] === value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+);
+
+var array_map = uncurryThis(
+ Array.prototype.map || function (callback, thisp) {
+ var self = this;
+ var collect = [];
+ array_reduce(self, function (undefined, value, index) {
+ collect.push(callback.call(thisp, value, index, self));
+ }, void 0);
+ return collect;
+ }
+);
+
+var object_create = Object.create || function (prototype) {
+ function Type() { }
+ Type.prototype = prototype;
+ return new Type();
+};
+
+var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
+
+var object_keys = Object.keys || function (object) {
+ var keys = [];
+ for (var key in object) {
+ if (object_hasOwnProperty(object, key)) {
+ keys.push(key);
+ }
+ }
+ return keys;
+};
+
+var object_toString = uncurryThis(Object.prototype.toString);
+
+function isObject(value) {
+ return value === Object(value);
+}
+
+// generator related shims
+
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
+function isStopIteration(exception) {
+ return (
+ object_toString(exception) === "[object StopIteration]" ||
+ exception instanceof QReturnValue
+ );
+}
+
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
+var QReturnValue;
+if (typeof ReturnValue !== "undefined") {
+ QReturnValue = ReturnValue;
+} else {
+ QReturnValue = function (value) {
+ this.value = value;
+ };
+}
+
+// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+// engine that has a deployed base of browsers that support generators.
+// However, SM's generators use the Python-inspired semantics of
+// outdated ES6 drafts. We would like to support ES6, but we'd also
+// like to make it possible to use generators in deployed browsers, so
+// we also support Python-style generators. At some point we can remove
+// this block.
+var hasES6Generators;
+try {
+ /* jshint evil: true, nonew: false */
+ new Function("(function* (){ yield 1; })");
+ hasES6Generators = true;
+} catch (e) {
+ hasES6Generators = false;
+}
+
+// long stack traces
+
+var STACK_JUMP_SEPARATOR = "From previous event:";
+
+function makeStackTraceLong(error, promise) {
+ // If possible, transform the error stack trace by removing Node and Q
+ // cruft, then concatenating with the stack trace of `promise`. See #57.
+ if (hasStacks &&
+ promise.stack &&
+ typeof error === "object" &&
+ error !== null &&
+ error.stack &&
+ error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
+ ) {
+ var stacks = [];
+ for (var p = promise; !!p; p = p.source) {
+ if (p.stack) {
+ stacks.unshift(p.stack);
+ }
+ }
+ stacks.unshift(error.stack);
+
+ var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
+ error.stack = filterStackString(concatedStacks);
+ }
+}
+
+function filterStackString(stackString) {
+ var lines = stackString.split("\n");
+ var desiredLines = [];
+ for (var i = 0; i < lines.length; ++i) {
+ var line = lines[i];
+
+ if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
+ desiredLines.push(line);
+ }
+ }
+ return desiredLines.join("\n");
+}
+
+function isNodeFrame(stackLine) {
+ return stackLine.indexOf("(module.js:") !== -1 ||
+ stackLine.indexOf("(node.js:") !== -1;
+}
+
+function getFileNameAndLineNumber(stackLine) {
+ // Named functions: "at functionName (filename:lineNumber:columnNumber)"
+ // In IE10 function name can have spaces ("Anonymous function") O_o
+ var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
+ if (attempt1) {
+ return [attempt1[1], Number(attempt1[2])];
+ }
+
+ // Anonymous functions: "at filename:lineNumber:columnNumber"
+ var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
+ if (attempt2) {
+ return [attempt2[1], Number(attempt2[2])];
+ }
+
+ // Firefox style: "function@filename:lineNumber or @filename:lineNumber"
+ var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
+ if (attempt3) {
+ return [attempt3[1], Number(attempt3[2])];
+ }
+}
+
+function isInternalFrame(stackLine) {
+ var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
+
+ if (!fileNameAndLineNumber) {
+ return false;
+ }
+
+ var fileName = fileNameAndLineNumber[0];
+ var lineNumber = fileNameAndLineNumber[1];
+
+ return fileName === qFileName &&
+ lineNumber >= qStartingLine &&
+ lineNumber <= qEndingLine;
+}
+
+// discover own file name and line number range for filtering stack
+// traces
+function captureLine() {
+ if (!hasStacks) {
+ return;
+ }
+
+ try {
+ throw new Error();
+ } catch (e) {
+ var lines = e.stack.split("\n");
+ var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
+ var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
+ if (!fileNameAndLineNumber) {
+ return;
+ }
+
+ qFileName = fileNameAndLineNumber[0];
+ return fileNameAndLineNumber[1];
+ }
+}
+
+function deprecate(callback, name, alternative) {
+ return function () {
+ if (typeof console !== "undefined" &&
+ typeof console.warn === "function") {
+ console.warn(name + " is deprecated, use " + alternative +
+ " instead.", new Error("").stack);
+ }
+ return callback.apply(callback, arguments);
+ };
+}
+
+// end of shims
+// beginning of real work
+
+/**
+ * Constructs a promise for an immediate reference, passes promises through, or
+ * coerces promises from different systems.
+ * @param value immediate reference or promise
+ */
+function Q(value) {
+ // If the object is already a Promise, return it directly. This enables
+ // the resolve function to both be used to created references from objects,
+ // but to tolerably coerce non-promises to promises.
+ if (isPromise(value)) {
+ return value;
+ }
+
+ // assimilate thenables
+ if (isPromiseAlike(value)) {
+ return coerce(value);
+ } else {
+ return fulfill(value);
+ }
+}
+Q.resolve = Q;
+
+/**
+ * Performs a task in a future turn of the event loop.
+ * @param {Function} task
+ */
+Q.nextTick = nextTick;
+
+/**
+ * Controls whether or not long stack traces will be on
+ */
+Q.longStackSupport = false;
+
+/**
+ * Constructs a {promise, resolve, reject} object.
+ *
+ * `resolve` is a callback to invoke with a more resolved value for the
+ * promise. To fulfill the promise, invoke `resolve` with any value that is
+ * not a thenable. To reject the promise, invoke `resolve` with a rejected
+ * thenable, or invoke `reject` with the reason directly. To resolve the
+ * promise to another thenable, thus putting it in the same state, invoke
+ * `resolve` with that other thenable.
+ */
+Q.defer = defer;
+function defer() {
+ // if "messages" is an "Array", that indicates that the promise has not yet
+ // been resolved. If it is "undefined", it has been resolved. Each
+ // element of the messages array is itself an array of complete arguments to
+ // forward to the resolved promise. We coerce the resolution value to a
+ // promise using the `resolve` function because it handles both fully
+ // non-thenable values and other thenables gracefully.
+ var messages = [], progressListeners = [], resolvedPromise;
+
+ var deferred = object_create(defer.prototype);
+ var promise = object_create(Promise.prototype);
+
+ promise.promiseDispatch = function (resolve, op, operands) {
+ var args = array_slice(arguments);
+ if (messages) {
+ messages.push(args);
+ if (op === "when" && operands[1]) { // progress operand
+ progressListeners.push(operands[1]);
+ }
+ } else {
+ nextTick(function () {
+ resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
+ });
+ }
+ };
+
+ // XXX deprecated
+ promise.valueOf = function () {
+ if (messages) {
+ return promise;
+ }
+ var nearerValue = nearer(resolvedPromise);
+ if (isPromise(nearerValue)) {
+ resolvedPromise = nearerValue; // shorten chain
+ }
+ return nearerValue;
+ };
+
+ promise.inspect = function () {
+ if (!resolvedPromise) {
+ return { state: "pending" };
+ }
+ return resolvedPromise.inspect();
+ };
+
+ if (Q.longStackSupport && hasStacks) {
+ try {
+ throw new Error();
+ } catch (e) {
+ // NOTE: don't try to use `Error.captureStackTrace` or transfer the
+ // accessor around; that causes memory leaks as per GH-111. Just
+ // reify the stack trace as a string ASAP.
+ //
+ // At the same time, cut off the first line; it's always just
+ // "[object Promise]\n", as per the `toString`.
+ promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
+ }
+ }
+
+ // NOTE: we do the checks for `resolvedPromise` in each method, instead of
+ // consolidating them into `become`, since otherwise we'd create new
+ // promises with the lines `become(whatever(value))`. See e.g. GH-252.
+
+ function become(newPromise) {
+ resolvedPromise = newPromise;
+ promise.source = newPromise;
+
+ array_reduce(messages, function (undefined, message) {
+ nextTick(function () {
+ newPromise.promiseDispatch.apply(newPromise, message);
+ });
+ }, void 0);
+
+ messages = void 0;
+ progressListeners = void 0;
+ }
+
+ deferred.promise = promise;
+ deferred.resolve = function (value) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ become(Q(value));
+ };
+
+ deferred.fulfill = function (value) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ become(fulfill(value));
+ };
+ deferred.reject = function (reason) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ become(reject(reason));
+ };
+ deferred.notify = function (progress) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ array_reduce(progressListeners, function (undefined, progressListener) {
+ nextTick(function () {
+ progressListener(progress);
+ });
+ }, void 0);
+ };
+
+ return deferred;
+}
+
+/**
+ * Creates a Node-style callback that will resolve or reject the deferred
+ * promise.
+ * @returns a nodeback
+ */
+defer.prototype.makeNodeResolver = function () {
+ var self = this;
+ return function (error, value) {
+ if (error) {
+ self.reject(error);
+ } else if (arguments.length > 2) {
+ self.resolve(array_slice(arguments, 1));
+ } else {
+ self.resolve(value);
+ }
+ };
+};
+
+/**
+ * @param resolver {Function} a function that returns nothing and accepts
+ * the resolve, reject, and notify functions for a deferred.
+ * @returns a promise that may be resolved with the given resolve and reject
+ * functions, or rejected by a thrown exception in resolver
+ */
+Q.promise = promise;
+function promise(resolver) {
+ if (typeof resolver !== "function") {
+ throw new TypeError("resolver must be a function.");
+ }
+ var deferred = defer();
+ try {
+ resolver(deferred.resolve, deferred.reject, deferred.notify);
+ } catch (reason) {
+ deferred.reject(reason);
+ }
+ return deferred.promise;
+}
+
+// XXX experimental. This method is a way to denote that a local value is
+// serializable and should be immediately dispatched to a remote upon request,
+// instead of passing a reference.
+Q.passByCopy = function (object) {
+ //freeze(object);
+ //passByCopies.set(object, true);
+ return object;
+};
+
+Promise.prototype.passByCopy = function () {
+ //freeze(object);
+ //passByCopies.set(object, true);
+ return this;
+};
+
+/**
+ * If two promises eventually fulfill to the same value, promises that value,
+ * but otherwise rejects.
+ * @param x {Any*}
+ * @param y {Any*}
+ * @returns {Any*} a promise for x and y if they are the same, but a rejection
+ * otherwise.
+ *
+ */
+Q.join = function (x, y) {
+ return Q(x).join(y);
+};
+
+Promise.prototype.join = function (that) {
+ return Q([this, that]).spread(function (x, y) {
+ if (x === y) {
+ // TODO: "===" should be Object.is or equiv
+ return x;
+ } else {
+ throw new Error("Can't join: not the same: " + x + " " + y);
+ }
+ });
+};
+
+/**
+ * Returns a promise for the first of an array of promises to become fulfilled.
+ * @param answers {Array[Any*]} promises to race
+ * @returns {Any*} the first promise to be fulfilled
+ */
+Q.race = race;
+function race(answerPs) {
+ return promise(function(resolve, reject) {
+ // Switch to this once we can assume at least ES5
+ // answerPs.forEach(function(answerP) {
+ // Q(answerP).then(resolve, reject);
+ // });
+ // Use this in the meantime
+ for (var i = 0, len = answerPs.length; i < len; i++) {
+ Q(answerPs[i]).then(resolve, reject);
+ }
+ });
+}
+
+Promise.prototype.race = function () {
+ return this.then(Q.race);
+};
+
+/**
+ * Constructs a Promise with a promise descriptor object and optional fallback
+ * function. The descriptor contains methods like when(rejected), get(name),
+ * set(name, value), post(name, args), and delete(name), which all
+ * return either a value, a promise for a value, or a rejection. The fallback
+ * accepts the operation name, a resolver, and any further arguments that would
+ * have been forwarded to the appropriate method above had a method been
+ * provided with the proper name. The API makes no guarantees about the nature
+ * of the returned object, apart from that it is usable whereever promises are
+ * bought and sold.
+ */
+Q.makePromise = Promise;
+function Promise(descriptor, fallback, inspect) {
+ if (fallback === void 0) {
+ fallback = function (op) {
+ return reject(new Error(
+ "Promise does not support operation: " + op
+ ));
+ };
+ }
+ if (inspect === void 0) {
+ inspect = function () {
+ return {state: "unknown"};
+ };
+ }
+
+ var promise = object_create(Promise.prototype);
+
+ promise.promiseDispatch = function (resolve, op, args) {
+ var result;
+ try {
+ if (descriptor[op]) {
+ result = descriptor[op].apply(promise, args);
+ } else {
+ result = fallback.call(promise, op, args);
+ }
+ } catch (exception) {
+ result = reject(exception);
+ }
+ if (resolve) {
+ resolve(result);
+ }
+ };
+
+ promise.inspect = inspect;
+
+ // XXX deprecated `valueOf` and `exception` support
+ if (inspect) {
+ var inspected = inspect();
+ if (inspected.state === "rejected") {
+ promise.exception = inspected.reason;
+ }
+
+ promise.valueOf = function () {
+ var inspected = inspect();
+ if (inspected.state === "pending" ||
+ inspected.state === "rejected") {
+ return promise;
+ }
+ return inspected.value;
+ };
+ }
+
+ return promise;
+}
+
+Promise.prototype.toString = function () {
+ return "[object Promise]";
+};
+
+Promise.prototype.then = function (fulfilled, rejected, progressed) {
+ var self = this;
+ var deferred = defer();
+ var done = false; // ensure the untrusted promise makes at most a
+ // single call to one of the callbacks
+
+ function _fulfilled(value) {
+ try {
+ return typeof fulfilled === "function" ? fulfilled(value) : value;
+ } catch (exception) {
+ return reject(exception);
+ }
+ }
+
+ function _rejected(exception) {
+ if (typeof rejected === "function") {
+ makeStackTraceLong(exception, self);
+ try {
+ return rejected(exception);
+ } catch (newException) {
+ return reject(newException);
+ }
+ }
+ return reject(exception);
+ }
+
+ function _progressed(value) {
+ return typeof progressed === "function" ? progressed(value) : value;
+ }
+
+ nextTick(function () {
+ self.promiseDispatch(function (value) {
+ if (done) {
+ return;
+ }
+ done = true;
+
+ deferred.resolve(_fulfilled(value));
+ }, "when", [function (exception) {
+ if (done) {
+ return;
+ }
+ done = true;
+
+ deferred.resolve(_rejected(exception));
+ }]);
+ });
+
+ // Progress propagator need to be attached in the current tick.
+ self.promiseDispatch(void 0, "when", [void 0, function (value) {
+ var newValue;
+ var threw = false;
+ try {
+ newValue = _progressed(value);
+ } catch (e) {
+ threw = true;
+ if (Q.onerror) {
+ Q.onerror(e);
+ } else {
+ throw e;
+ }
+ }
+
+ if (!threw) {
+ deferred.notify(newValue);
+ }
+ }]);
+
+ return deferred.promise;
+};
+
+/**
+ * Registers an observer on a promise.
+ *
+ * Guarantees:
+ *
+ * 1. that fulfilled and rejected will be called only once.
+ * 2. that either the fulfilled callback or the rejected callback will be
+ * called, but not both.
+ * 3. that fulfilled and rejected will not be called in this turn.
+ *
+ * @param value promise or immediate reference to observe
+ * @param fulfilled function to be called with the fulfilled value
+ * @param rejected function to be called with the rejection exception
+ * @param progressed function to be called on any progress notifications
+ * @return promise for the return value from the invoked callback
+ */
+Q.when = when;
+function when(value, fulfilled, rejected, progressed) {
+ return Q(value).then(fulfilled, rejected, progressed);
+}
+
+Promise.prototype.thenResolve = function (value) {
+ return this.then(function () { return value; });
+};
+
+Q.thenResolve = function (promise, value) {
+ return Q(promise).thenResolve(value);
+};
+
+Promise.prototype.thenReject = function (reason) {
+ return this.then(function () { throw reason; });
+};
+
+Q.thenReject = function (promise, reason) {
+ return Q(promise).thenReject(reason);
+};
+
+/**
+ * If an object is not a promise, it is as "near" as possible.
+ * If a promise is rejected, it is as "near" as possible too.
+ * If it’s a fulfilled promise, the fulfillment value is nearer.
+ * If it’s a deferred promise and the deferred has been resolved, the
+ * resolution is "nearer".
+ * @param object
+ * @returns most resolved (nearest) form of the object
+ */
+
+// XXX should we re-do this?
+Q.nearer = nearer;
+function nearer(value) {
+ if (isPromise(value)) {
+ var inspected = value.inspect();
+ if (inspected.state === "fulfilled") {
+ return inspected.value;
+ }
+ }
+ return value;
+}
+
+/**
+ * @returns whether the given object is a promise.
+ * Otherwise it is a fulfilled value.
+ */
+Q.isPromise = isPromise;
+function isPromise(object) {
+ return isObject(object) &&
+ typeof object.promiseDispatch === "function" &&
+ typeof object.inspect === "function";
+}
+
+Q.isPromiseAlike = isPromiseAlike;
+function isPromiseAlike(object) {
+ return isObject(object) && typeof object.then === "function";
+}
+
+/**
+ * @returns whether the given object is a pending promise, meaning not
+ * fulfilled or rejected.
+ */
+Q.isPending = isPending;
+function isPending(object) {
+ return isPromise(object) && object.inspect().state === "pending";
+}
+
+Promise.prototype.isPending = function () {
+ return this.inspect().state === "pending";
+};
+
+/**
+ * @returns whether the given object is a value or fulfilled
+ * promise.
+ */
+Q.isFulfilled = isFulfilled;
+function isFulfilled(object) {
+ return !isPromise(object) || object.inspect().state === "fulfilled";
+}
+
+Promise.prototype.isFulfilled = function () {
+ return this.inspect().state === "fulfilled";
+};
+
+/**
+ * @returns whether the given object is a rejected promise.
+ */
+Q.isRejected = isRejected;
+function isRejected(object) {
+ return isPromise(object) && object.inspect().state === "rejected";
+}
+
+Promise.prototype.isRejected = function () {
+ return this.inspect().state === "rejected";
+};
+
+//// BEGIN UNHANDLED REJECTION TRACKING
+
+// This promise library consumes exceptions thrown in handlers so they can be
+// handled by a subsequent promise. The exceptions get added to this array when
+// they are created, and removed when they are handled. Note that in ES6 or
+// shimmed environments, this would naturally be a `Set`.
+var unhandledReasons = [];
+var unhandledRejections = [];
+var unhandledReasonsDisplayed = false;
+var trackUnhandledRejections = true;
+function displayUnhandledReasons() {
+ if (
+ !unhandledReasonsDisplayed &&
+ typeof window !== "undefined" &&
+ window.console
+ ) {
+ console.warn("[Q] Unhandled rejection reasons (should be empty):",
+ unhandledReasons);
+ }
+
+ unhandledReasonsDisplayed = true;
+}
+
+function logUnhandledReasons() {
+ for (var i = 0; i < unhandledReasons.length; i++) {
+ var reason = unhandledReasons[i];
+ console.warn("Unhandled rejection reason:", reason);
+ }
+}
+
+function resetUnhandledRejections() {
+ unhandledReasons.length = 0;
+ unhandledRejections.length = 0;
+ unhandledReasonsDisplayed = false;
+
+ if (!trackUnhandledRejections) {
+ trackUnhandledRejections = true;
+
+ // Show unhandled rejection reasons if Node exits without handling an
+ // outstanding rejection. (Note that Browserify presently produces a
+ // `process` global without the `EventEmitter` `on` method.)
+ if (typeof process !== "undefined" && process.on) {
+ process.on("exit", logUnhandledReasons);
+ }
+ }
+}
+
+function trackRejection(promise, reason) {
+ if (!trackUnhandledRejections) {
+ return;
+ }
+
+ unhandledRejections.push(promise);
+ if (reason && typeof reason.stack !== "undefined") {
+ unhandledReasons.push(reason.stack);
+ } else {
+ unhandledReasons.push("(no stack) " + reason);
+ }
+ displayUnhandledReasons();
+}
+
+function untrackRejection(promise) {
+ if (!trackUnhandledRejections) {
+ return;
+ }
+
+ var at = array_indexOf(unhandledRejections, promise);
+ if (at !== -1) {
+ unhandledRejections.splice(at, 1);
+ unhandledReasons.splice(at, 1);
+ }
+}
+
+Q.resetUnhandledRejections = resetUnhandledRejections;
+
+Q.getUnhandledReasons = function () {
+ // Make a copy so that consumers can't interfere with our internal state.
+ return unhandledReasons.slice();
+};
+
+Q.stopUnhandledRejectionTracking = function () {
+ resetUnhandledRejections();
+ if (typeof process !== "undefined" && process.on) {
+ process.removeListener("exit", logUnhandledReasons);
+ }
+ trackUnhandledRejections = false;
+};
+
+resetUnhandledRejections();
+
+//// END UNHANDLED REJECTION TRACKING
+
+/**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+Q.reject = reject;
+function reject(reason) {
+ var rejection = Promise({
+ "when": function (rejected) {
+ // note that the error has been handled
+ if (rejected) {
+ untrackRejection(this);
+ }
+ return rejected ? rejected(reason) : this;
+ }
+ }, function fallback() {
+ return this;
+ }, function inspect() {
+ return { state: "rejected", reason: reason };
+ });
+
+ // Note that the reason has not been handled.
+ trackRejection(rejection, reason);
+
+ return rejection;
+}
+
+/**
+ * Constructs a fulfilled promise for an immediate reference.
+ * @param value immediate reference
+ */
+Q.fulfill = fulfill;
+function fulfill(value) {
+ return Promise({
+ "when": function () {
+ return value;
+ },
+ "get": function (name) {
+ return value[name];
+ },
+ "set": function (name, rhs) {
+ value[name] = rhs;
+ },
+ "delete": function (name) {
+ delete value[name];
+ },
+ "post": function (name, args) {
+ // Mark Miller proposes that post with no name should apply a
+ // promised function.
+ if (name === null || name === void 0) {
+ return value.apply(void 0, args);
+ } else {
+ return value[name].apply(value, args);
+ }
+ },
+ "apply": function (thisp, args) {
+ return value.apply(thisp, args);
+ },
+ "keys": function () {
+ return object_keys(value);
+ }
+ }, void 0, function inspect() {
+ return { state: "fulfilled", value: value };
+ });
+}
+
+/**
+ * Converts thenables to Q promises.
+ * @param promise thenable promise
+ * @returns a Q promise
+ */
+function coerce(promise) {
+ var deferred = defer();
+ nextTick(function () {
+ try {
+ promise.then(deferred.resolve, deferred.reject, deferred.notify);
+ } catch (exception) {
+ deferred.reject(exception);
+ }
+ });
+ return deferred.promise;
+}
+
+/**
+ * Annotates an object such that it will never be
+ * transferred away from this process over any promise
+ * communication channel.
+ * @param object
+ * @returns promise a wrapping of that object that
+ * additionally responds to the "isDef" message
+ * without a rejection.
+ */
+Q.master = master;
+function master(object) {
+ return Promise({
+ "isDef": function () {}
+ }, function fallback(op, args) {
+ return dispatch(object, op, args);
+ }, function () {
+ return Q(object).inspect();
+ });
+}
+
+/**
+ * Spreads the values of a promised array of arguments into the
+ * fulfillment callback.
+ * @param fulfilled callback that receives variadic arguments from the
+ * promised array
+ * @param rejected callback that receives the exception if the promise
+ * is rejected.
+ * @returns a promise for the return value or thrown exception of
+ * either callback.
+ */
+Q.spread = spread;
+function spread(value, fulfilled, rejected) {
+ return Q(value).spread(fulfilled, rejected);
+}
+
+Promise.prototype.spread = function (fulfilled, rejected) {
+ return this.all().then(function (array) {
+ return fulfilled.apply(void 0, array);
+ }, rejected);
+};
+
+/**
+ * The async function is a decorator for generator functions, turning
+ * them into asynchronous generators. Although generators are only part
+ * of the newest ECMAScript 6 drafts, this code does not cause syntax
+ * errors in older engines. This code should continue to work and will
+ * in fact improve over time as the language improves.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled. SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form. This function
+ * works on both kinds of generators.
+ *
+ * Decorates a generator function such that:
+ * - it may yield promises
+ * - execution will continue when that promise is fulfilled
+ * - the value of the yield expression will be the fulfilled value
+ * - it returns a promise for the return value (when the generator
+ * stops iterating)
+ * - the decorated function returns a promise for the return value
+ * of the generator or the first rejected promise among those
+ * yielded.
+ * - if an error is thrown in the generator, it propagates through
+ * every following yield until it is caught, or until it escapes
+ * the generator function altogether, and is translated into a
+ * rejection for the promise returned by the decorated generator.
+ */
+Q.async = async;
+function async(makeGenerator) {
+ return function () {
+ // when verb is "send", arg is a value
+ // when verb is "throw", arg is an exception
+ function continuer(verb, arg) {
+ var result;
+ if (hasES6Generators) {
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
+ return reject(exception);
+ }
+ if (result.done) {
+ return result.value;
+ } else {
+ return when(result.value, callback, errback);
+ }
+ } else {
+ // FIXME: Remove this case when SM does ES6 generators.
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
+ if (isStopIteration(exception)) {
+ return exception.value;
+ } else {
+ return reject(exception);
+ }
+ }
+ return when(result, callback, errback);
+ }
+ }
+ var generator = makeGenerator.apply(this, arguments);
+ var callback = continuer.bind(continuer, "next");
+ var errback = continuer.bind(continuer, "throw");
+ return callback();
+ };
+}
+
+/**
+ * The spawn function is a small wrapper around async that immediately
+ * calls the generator and also ends the promise chain, so that any
+ * unhandled errors are thrown instead of forwarded to the error
+ * handler. This is useful because it's extremely common to run
+ * generators at the top-level to work with libraries.
+ */
+Q.spawn = spawn;
+function spawn(makeGenerator) {
+ Q.done(Q.async(makeGenerator)());
+}
+
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
+/**
+ * Throws a ReturnValue exception to stop an asynchronous generator.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey. In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
+ * @param value the return value for the surrounding generator
+ * @throws ReturnValue exception with the value.
+ * @example
+ * // ES6 style
+ * Q.async(function* () {
+ * var foo = yield getFooPromise();
+ * var bar = yield getBarPromise();
+ * return foo + bar;
+ * })
+ * // Older SpiderMonkey style
+ * Q.async(function () {
+ * var foo = yield getFooPromise();
+ * var bar = yield getBarPromise();
+ * Q.return(foo + bar);
+ * })
+ */
+Q["return"] = _return;
+function _return(value) {
+ throw new QReturnValue(value);
+}
+
+/**
+ * The promised function decorator ensures that any promise arguments
+ * are settled and passed as values (`this` is also settled and passed
+ * as a value). It will also ensure that the result of a function is
+ * always a promise.
+ *
+ * @example
+ * var add = Q.promised(function (a, b) {
+ * return a + b;
+ * });
+ * add(Q(a), Q(B));
+ *
+ * @param {function} callback The function to decorate
+ * @returns {function} a function that has been decorated.
+ */
+Q.promised = promised;
+function promised(callback) {
+ return function () {
+ return spread([this, all(arguments)], function (self, args) {
+ return callback.apply(self, args);
+ });
+ };
+}
+
+/**
+ * sends a message to a value in a future turn
+ * @param object* the recipient
+ * @param op the name of the message operation, e.g., "when",
+ * @param args further arguments to be forwarded to the operation
+ * @returns result {Promise} a promise for the result of the operation
+ */
+Q.dispatch = dispatch;
+function dispatch(object, op, args) {
+ return Q(object).dispatch(op, args);
+}
+
+Promise.prototype.dispatch = function (op, args) {
+ var self = this;
+ var deferred = defer();
+ nextTick(function () {
+ self.promiseDispatch(deferred.resolve, op, args);
+ });
+ return deferred.promise;
+};
+
+/**
+ * Gets the value of a property in a future turn.
+ * @param object promise or immediate reference for target object
+ * @param name name of property to get
+ * @return promise for the property value
+ */
+Q.get = function (object, key) {
+ return Q(object).dispatch("get", [key]);
+};
+
+Promise.prototype.get = function (key) {
+ return this.dispatch("get", [key]);
+};
+
+/**
+ * Sets the value of a property in a future turn.
+ * @param object promise or immediate reference for object object
+ * @param name name of property to set
+ * @param value new value of property
+ * @return promise for the return value
+ */
+Q.set = function (object, key, value) {
+ return Q(object).dispatch("set", [key, value]);
+};
+
+Promise.prototype.set = function (key, value) {
+ return this.dispatch("set", [key, value]);
+};
+
+/**
+ * Deletes a property in a future turn.
+ * @param object promise or immediate reference for target object
+ * @param name name of property to delete
+ * @return promise for the return value
+ */
+Q.del = // XXX legacy
+Q["delete"] = function (object, key) {
+ return Q(object).dispatch("delete", [key]);
+};
+
+Promise.prototype.del = // XXX legacy
+Promise.prototype["delete"] = function (key) {
+ return this.dispatch("delete", [key]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object promise or immediate reference for target object
+ * @param name name of method to invoke
+ * @param value a value to post, typically an array of
+ * invocation arguments for promises that
+ * are ultimately backed with `resolve` values,
+ * as opposed to those backed with URLs
+ * wherein the posted value can be any
+ * JSON serializable object.
+ * @return promise for the return value
+ */
+// bound locally because it is used by other methods
+Q.mapply = // XXX As proposed by "Redsandro"
+Q.post = function (object, name, args) {
+ return Q(object).dispatch("post", [name, args]);
+};
+
+Promise.prototype.mapply = // XXX As proposed by "Redsandro"
+Promise.prototype.post = function (name, args) {
+ return this.dispatch("post", [name, args]);
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param object promise or immediate reference for target object
+ * @param name name of method to invoke
+ * @param ...args array of invocation arguments
+ * @return promise for the return value
+ */
+Q.send = // XXX Mark Miller's proposed parlance
+Q.mcall = // XXX As proposed by "Redsandro"
+Q.invoke = function (object, name /*...args*/) {
+ return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
+};
+
+Promise.prototype.send = // XXX Mark Miller's proposed parlance
+Promise.prototype.mcall = // XXX As proposed by "Redsandro"
+Promise.prototype.invoke = function (name /*...args*/) {
+ return this.dispatch("post", [name, array_slice(arguments, 1)]);
+};
+
+/**
+ * Applies the promised function in a future turn.
+ * @param object promise or immediate reference for target function
+ * @param args array of application arguments
+ */
+Q.fapply = function (object, args) {
+ return Q(object).dispatch("apply", [void 0, args]);
+};
+
+Promise.prototype.fapply = function (args) {
+ return this.dispatch("apply", [void 0, args]);
+};
+
+/**
+ * Calls the promised function in a future turn.
+ * @param object promise or immediate reference for target function
+ * @param ...args array of application arguments
+ */
+Q["try"] =
+Q.fcall = function (object /* ...args*/) {
+ return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
+};
+
+Promise.prototype.fcall = function (/*...args*/) {
+ return this.dispatch("apply", [void 0, array_slice(arguments)]);
+};
+
+/**
+ * Binds the promised function, transforming return values into a fulfilled
+ * promise and thrown errors into a rejected one.
+ * @param object promise or immediate reference for target function
+ * @param ...args array of application arguments
+ */
+Q.fbind = function (object /*...args*/) {
+ var promise = Q(object);
+ var args = array_slice(arguments, 1);
+ return function fbound() {
+ return promise.dispatch("apply", [
+ this,
+ args.concat(array_slice(arguments))
+ ]);
+ };
+};
+Promise.prototype.fbind = function (/*...args*/) {
+ var promise = this;
+ var args = array_slice(arguments);
+ return function fbound() {
+ return promise.dispatch("apply", [
+ this,
+ args.concat(array_slice(arguments))
+ ]);
+ };
+};
+
+/**
+ * Requests the names of the owned properties of a promised
+ * object in a future turn.
+ * @param object promise or immediate reference for target object
+ * @return promise for the keys of the eventually settled object
+ */
+Q.keys = function (object) {
+ return Q(object).dispatch("keys", []);
+};
+
+Promise.prototype.keys = function () {
+ return this.dispatch("keys", []);
+};
+
+/**
+ * Turns an array of promises into a promise for an array. If any of
+ * the promises gets rejected, the whole array is rejected immediately.
+ * @param {Array*} an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns a promise for an array of the corresponding values
+ */
+// By Mark Miller
+// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
+Q.all = all;
+function all(promises) {
+ return when(promises, function (promises) {
+ var countDown = 0;
+ var deferred = defer();
+ array_reduce(promises, function (undefined, promise, index) {
+ var snapshot;
+ if (
+ isPromise(promise) &&
+ (snapshot = promise.inspect()).state === "fulfilled"
+ ) {
+ promises[index] = snapshot.value;
+ } else {
+ ++countDown;
+ when(
+ promise,
+ function (value) {
+ promises[index] = value;
+ if (--countDown === 0) {
+ deferred.resolve(promises);
+ }
+ },
+ deferred.reject,
+ function (progress) {
+ deferred.notify({ index: index, value: progress });
+ }
+ );
+ }
+ }, void 0);
+ if (countDown === 0) {
+ deferred.resolve(promises);
+ }
+ return deferred.promise;
+ });
+}
+
+Promise.prototype.all = function () {
+ return all(this);
+};
+
+/**
+ * Waits for all promises to be settled, either fulfilled or
+ * rejected. This is distinct from `all` since that would stop
+ * waiting at the first rejection. The promise returned by
+ * `allResolved` will never be rejected.
+ * @param promises a promise for an array (or an array) of promises
+ * (or values)
+ * @return a promise for an array of promises
+ */
+Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
+function allResolved(promises) {
+ return when(promises, function (promises) {
+ promises = array_map(promises, Q);
+ return when(all(array_map(promises, function (promise) {
+ return when(promise, noop, noop);
+ })), function () {
+ return promises;
+ });
+ });
+}
+
+Promise.prototype.allResolved = function () {
+ return allResolved(this);
+};
+
+/**
+ * @see Promise#allSettled
+ */
+Q.allSettled = allSettled;
+function allSettled(promises) {
+ return Q(promises).allSettled();
+}
+
+/**
+ * Turns an array of promises into a promise for an array of their states (as
+ * returned by `inspect`) when they have all settled.
+ * @param {Array[Any*]} values an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns {Array[State]} an array of states for the respective values.
+ */
+Promise.prototype.allSettled = function () {
+ return this.then(function (promises) {
+ return all(array_map(promises, function (promise) {
+ promise = Q(promise);
+ function regardless() {
+ return promise.inspect();
+ }
+ return promise.then(regardless, regardless);
+ }));
+ });
+};
+
+/**
+ * Captures the failure of a promise, giving an oportunity to recover
+ * with a callback. If the given promise is fulfilled, the returned
+ * promise is fulfilled.
+ * @param {Any*} promise for something
+ * @param {Function} callback to fulfill the returned promise if the
+ * given promise is rejected
+ * @returns a promise for the return value of the callback
+ */
+Q.fail = // XXX legacy
+Q["catch"] = function (object, rejected) {
+ return Q(object).then(void 0, rejected);
+};
+
+Promise.prototype.fail = // XXX legacy
+Promise.prototype["catch"] = function (rejected) {
+ return this.then(void 0, rejected);
+};
+
+/**
+ * Attaches a listener that can respond to progress notifications from a
+ * promise's originating deferred. This listener receives the exact arguments
+ * passed to ``deferred.notify``.
+ * @param {Any*} promise for something
+ * @param {Function} callback to receive any progress notifications
+ * @returns the given promise, unchanged
+ */
+Q.progress = progress;
+function progress(object, progressed) {
+ return Q(object).then(void 0, void 0, progressed);
+}
+
+Promise.prototype.progress = function (progressed) {
+ return this.then(void 0, void 0, progressed);
+};
+
+/**
+ * Provides an opportunity to observe the settling of a promise,
+ * regardless of whether the promise is fulfilled or rejected. Forwards
+ * the resolution to the returned promise when the callback is done.
+ * The callback can return a promise to defer completion.
+ * @param {Any*} promise
+ * @param {Function} callback to observe the resolution of the given
+ * promise, takes no arguments.
+ * @returns a promise for the resolution of the given promise when
+ * ``fin`` is done.
+ */
+Q.fin = // XXX legacy
+Q["finally"] = function (object, callback) {
+ return Q(object)["finally"](callback);
+};
+
+Promise.prototype.fin = // XXX legacy
+Promise.prototype["finally"] = function (callback) {
+ callback = Q(callback);
+ return this.then(function (value) {
+ return callback.fcall().then(function () {
+ return value;
+ });
+ }, function (reason) {
+ // TODO attempt to recycle the rejection with "this".
+ return callback.fcall().then(function () {
+ throw reason;
+ });
+ });
+};
+
+/**
+ * Terminates a chain of promises, forcing rejections to be
+ * thrown as exceptions.
+ * @param {Any*} promise at the end of a chain of promises
+ * @returns nothing
+ */
+Q.done = function (object, fulfilled, rejected, progress) {
+ return Q(object).done(fulfilled, rejected, progress);
+};
+
+Promise.prototype.done = function (fulfilled, rejected, progress) {
+ var onUnhandledError = function (error) {
+ // forward to a future turn so that ``when``
+ // does not catch it and turn it into a rejection.
+ nextTick(function () {
+ makeStackTraceLong(error, promise);
+ if (Q.onerror) {
+ Q.onerror(error);
+ } else {
+ throw error;
+ }
+ });
+ };
+
+ // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
+ var promise = fulfilled || rejected || progress ?
+ this.then(fulfilled, rejected, progress) :
+ this;
+
+ if (typeof process === "object" && process && process.domain) {
+ onUnhandledError = process.domain.bind(onUnhandledError);
+ }
+
+ promise.then(void 0, onUnhandledError);
+};
+
+/**
+ * Causes a promise to be rejected if it does not get fulfilled before
+ * some milliseconds time out.
+ * @param {Any*} promise
+ * @param {Number} milliseconds timeout
+ * @param {String} custom error message (optional)
+ * @returns a promise for the resolution of the given promise if it is
+ * fulfilled before the timeout, otherwise rejected.
+ */
+Q.timeout = function (object, ms, message) {
+ return Q(object).timeout(ms, message);
+};
+
+Promise.prototype.timeout = function (ms, message) {
+ var deferred = defer();
+ var timeoutId = setTimeout(function () {
+ deferred.reject(new Error(message || "Timed out after " + ms + " ms"));
+ }, ms);
+
+ this.then(function (value) {
+ clearTimeout(timeoutId);
+ deferred.resolve(value);
+ }, function (exception) {
+ clearTimeout(timeoutId);
+ deferred.reject(exception);
+ }, deferred.notify);
+
+ return deferred.promise;
+};
+
+/**
+ * Returns a promise for the given value (or promised value), some
+ * milliseconds after it resolved. Passes rejections immediately.
+ * @param {Any*} promise
+ * @param {Number} milliseconds
+ * @returns a promise for the resolution of the given promise after milliseconds
+ * time has elapsed since the resolution of the given promise.
+ * If the given promise rejects, that is passed immediately.
+ */
+Q.delay = function (object, timeout) {
+ if (timeout === void 0) {
+ timeout = object;
+ object = void 0;
+ }
+ return Q(object).delay(timeout);
+};
+
+Promise.prototype.delay = function (timeout) {
+ return this.then(function (value) {
+ var deferred = defer();
+ setTimeout(function () {
+ deferred.resolve(value);
+ }, timeout);
+ return deferred.promise;
+ });
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided as an array, and returns a promise.
+ *
+ * Q.nfapply(FS.readFile, [__filename])
+ * .then(function (content) {
+ * })
+ *
+ */
+Q.nfapply = function (callback, args) {
+ return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfapply = function (args) {
+ var deferred = defer();
+ var nodeArgs = array_slice(args);
+ nodeArgs.push(deferred.makeNodeResolver());
+ this.fapply(nodeArgs).fail(deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * Passes a continuation to a Node function, which is called with the given
+ * arguments provided individually, and returns a promise.
+ * @example
+ * Q.nfcall(FS.readFile, __filename)
+ * .then(function (content) {
+ * })
+ *
+ */
+Q.nfcall = function (callback /*...args*/) {
+ var args = array_slice(arguments, 1);
+ return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfcall = function (/*...args*/) {
+ var nodeArgs = array_slice(arguments);
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ this.fapply(nodeArgs).fail(deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * Wraps a NodeJS continuation passing function and returns an equivalent
+ * version that returns a promise.
+ * @example
+ * Q.nfbind(FS.readFile, __filename)("utf-8")
+ * .then(console.log)
+ * .done()
+ */
+Q.nfbind =
+Q.denodeify = function (callback /*...args*/) {
+ var baseArgs = array_slice(arguments, 1);
+ return function () {
+ var nodeArgs = baseArgs.concat(array_slice(arguments));
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ Q(callback).fapply(nodeArgs).fail(deferred.reject);
+ return deferred.promise;
+ };
+};
+
+Promise.prototype.nfbind =
+Promise.prototype.denodeify = function (/*...args*/) {
+ var args = array_slice(arguments);
+ args.unshift(this);
+ return Q.denodeify.apply(void 0, args);
+};
+
+Q.nbind = function (callback, thisp /*...args*/) {
+ var baseArgs = array_slice(arguments, 2);
+ return function () {
+ var nodeArgs = baseArgs.concat(array_slice(arguments));
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ function bound() {
+ return callback.apply(thisp, arguments);
+ }
+ Q(bound).fapply(nodeArgs).fail(deferred.reject);
+ return deferred.promise;
+ };
+};
+
+Promise.prototype.nbind = function (/*thisp, ...args*/) {
+ var args = array_slice(arguments, 0);
+ args.unshift(this);
+ return Q.nbind.apply(void 0, args);
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback with a given array of arguments, plus a provided callback.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param {Array} args arguments to pass to the method; the callback
+ * will be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nmapply = // XXX As proposed by "Redsandro"
+Q.npost = function (object, name, args) {
+ return Q(object).npost(name, args);
+};
+
+Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
+Promise.prototype.npost = function (name, args) {
+ var nodeArgs = array_slice(args || []);
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * Calls a method of a Node-style object that accepts a Node-style
+ * callback, forwarding the given variadic arguments, plus a provided
+ * callback argument.
+ * @param object an object that has the named method
+ * @param {String} name name of the method of object
+ * @param ...args arguments to pass to the method; the callback will
+ * be provided by Q and appended to these arguments.
+ * @returns a promise for the value or error
+ */
+Q.nsend = // XXX Based on Mark Miller's proposed "send"
+Q.nmcall = // XXX Based on "Redsandro's" proposal
+Q.ninvoke = function (object, name /*...args*/) {
+ var nodeArgs = array_slice(arguments, 2);
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+ return deferred.promise;
+};
+
+Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
+Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
+Promise.prototype.ninvoke = function (name /*...args*/) {
+ var nodeArgs = array_slice(arguments, 1);
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * If a function would like to support both Node continuation-passing-style and
+ * promise-returning-style, it can end its internal promise chain with
+ * `nodeify(nodeback)`, forwarding the optional nodeback argument. If the user
+ * elects to use a nodeback, the result will be sent there. If they do not
+ * pass a nodeback, they will receive the result promise.
+ * @param object a result (or a promise for a result)
+ * @param {Function} nodeback a Node.js-style callback
+ * @returns either the promise or nothing
+ */
+Q.nodeify = nodeify;
+function nodeify(object, nodeback) {
+ return Q(object).nodeify(nodeback);
+}
+
+Promise.prototype.nodeify = function (nodeback) {
+ if (nodeback) {
+ this.then(function (value) {
+ nextTick(function () {
+ nodeback(null, value);
+ });
+ }, function (error) {
+ nextTick(function () {
+ nodeback(error);
+ });
+ });
+ } else {
+ return this;
+ }
+};
+
+// All code before this point will be filtered from stack traces.
+var qEndingLine = captureLine();
+
+return Q;
+
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js
new file mode 100644
index 0000000..7f7523c
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/helper.js
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+
+/**
+ * Utility functions
+ */
+use(function() {
+
+ return {
+ /**
+ *
+ * Convert a java Map instance to a JS object
+ * @param {object} javaMap - instance of java.lang.Map
+ * @return {{}} the resulting object
+ * @ignore
+ */
+ mapToObject: function (javaMap) {
+ var props = {};
+ var it = javaMap.entrySet().iterator();
+ while (it.hasNext()) {
+ var entry = it.next();
+ props[entry.getKey()] = entry.getValue();
+ }
+ return props;
+ }
+ };
+});
\ No newline at end of file
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js
new file mode 100644
index 0000000..65b08ae
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/promise.js
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+
+/**
+ * Wrapper factory that transforms objects
+ * obtained synchronously in promises
+ */
+use(function() {
+ return function(Q) {
+ return {
+ success: function(x) {
+ return Q(x);
+ },
+
+ failure: function(err) {
+ return Q.reject(err);
+ }
+ };
+ }
+});
\ No newline at end of file
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js
new file mode 100644
index 0000000..1a75d2f
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/request.js
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+use(function(_) {
+
+ function convertParams(paramMap) {
+ var result = {};
+ var it = paramMap.entrySet().iterator();
+ while (it.hasNext()) {
+ var entry = it.next();
+ var paramName = entry.getKey();
+ var paramValues = entry.getValue();
+ if (paramValues) {
+ paramValues = paramValues.map(function (param) {
+ return param.getString();
+ });
+ }
+ result[paramName] = paramValues;
+ }
+ return result;
+ }
+
+ /**
+ * @constructor
+ * @class Properties that contain parts of the request
+ * @name RequestPathInfo
+ * @param {object} nativePathInfo The native path info object
+ */
+ function RequestPathInfo(nativePathInfo) {
+ /** @private */
+ this.nativePathInfo = nativePathInfo;
+ }
+
+ Object.defineProperties(RequestPathInfo.prototype, {
+
+ /**
+ * The resource path
+ * @name RequestPathInfo~resourcePath
+ * @type {String}
+ * @member
+ */
+ resourcePath: {
+ get: function() {
+ return this.nativePathInfo.getResourcePath();
+ }
+ },
+
+ /**
+ * The extension in the path
+ * @name RequestPathInfo~extension
+ * @type {String}
+ * @member
+ */
+ extension: {
+ get: function() {
+ return this.nativePathInfo.getExtension();
+ }
+ },
+
+ /**
+ * The selector string segment
+ * @name RequestPathInfo~selectorString
+ * @type {String}
+ * @member
+ */
+ selectorString: {
+ get: function() {
+ return this.nativePathInfo.getSelectorString();
+ }
+ },
+
+ /**
+ * The selectors in the request
+ * @name RequestPathInfo~selectors
+ * @type {Array.<String>}
+ * @member
+ */
+ selectors: {
+ get: function() {
+ return this.nativePathInfo.getSelectors();
+ }
+ },
+
+ /**
+ * The suffix in the request path
+ * @name RequestPathInfo~suffix
+ * @type {String}
+ * @member
+ */
+ suffix: {
+ get: function() {
+ return this.nativePathInfo.getSuffix();
+ }
+ }
+ });
+
+ /**
+ * @constructor
+ * @name Request
+ * @class The request class
+ * @param {object} nativeRequest The nativeResource request object
+ */
+ function Request(nativeRequest) {
+ /** @private */
+ this.nativeRequest = nativeRequest;
+
+ /**
+ * A map of the parameters in this request
+ * @name Request~parameters
+ * @type {object.<string, string>}
+ * @member
+ */
+ this.parameters = convertParams(nativeRequest.getRequestParameterMap());
+
+ /**
+ * The path info associated with this request
+ * @name Request~pathInfo
+ * @type {RequestPathInfo}
+ * @member
+ */
+ this.pathInfo = new RequestPathInfo(nativeRequest.getRequestPathInfo());
+ }
+
+ return Request;
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js
new file mode 100644
index 0000000..ba0eb0a
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/resource.js
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+use(['helper.js'], function(helper) {
+
+ function getParentPath(path) {
+ var index = path.lastIndexOf('/');
+ if (index == -1) {
+ return null;
+ }
+ return path.substring(0, index);
+ }
+
+
+ function getProperties(nativeResource) {
+ var valueMap = nativeResource.adaptTo(Packages.org.apache.sling.api.resource.ValueMap);
+ return (valueMap) ? helper.mapToObject(valueMap) : {};
+ }
+
+ /**
+ * @name Resource
+ * @constructor
+ * @class The Resource class
+ * @param {object} nativeResource The nativeResource resource object
+ */
+ function Resource(nativeResource, promise) {
+ /**
+ * The absolute path for this resource
+ * @name Resource~path
+ * @member
+ * @type {string}
+ */
+ this.path = nativeResource.getPath();
+
+ /**
+ * The map of properties for this object
+ * @name Resource~properties
+ * @member
+ * @type {object.<string, object>}
+ */
+ this.properties = getProperties(nativeResource);
+
+ /** @private */
+ this.nativeResource = nativeResource;
+
+ if (!promise) {
+ throw new Error('No promise library provided');
+ }
+ this._promise = promise;
+ }
+
+ Resource.prototype = /** @lends Resource.prototype */ {
+ constructor: Resource,
+
+ /**
+ * Get the parent resource
+ * @return {promise.<Resource>} a promise with the parent of this resource, or null if
+ * the resource has no parent
+ */
+ getParent: function() {
+ var parentPath = getParentPath(this.path);
+ if (!parentPath) {
+ return null;
+ }
+ var resolver = this.nativeResource.getResourceResolver();
+ var parent = resolver.resolve(parentPath);
+ return this._promise.success(new Resource(parent, this._promise));
+ },
+
+ /**
+ * Get the children of this resource
+ * @return {promise.<array.<Resource>>} a promise with the array of children resource
+ */
+ getChildren: function() {
+ var resolver = this.nativeResource.getResourceResolver();
+ var children = [];
+ var it = resolver.listChildren(this.nativeResource);
+ var promise = this._promise;
+ while (it.hasNext()) {
+ var childNativeResource = it.next();
+ children.push(new Resource(childNativeResource, promise));
+ }
+ return this._promise.success(children);
+ },
+
+ /**
+ * Returns the name of this resource. The name of a resource is the last segment of the path.
+ * @returns {string} the name of this resource
+ */
+ getName: function () {
+ var index = this.path.lastIndexOf('/');
+ if (index == -1) {
+ return this.path;
+ }
+ return this.path.substring(index + 1);
+ },
+
+ /**
+ * The resource type is meant to point to rendering/processing scripts, editing dialogs, etc.
+ * @return {string} the resource type of this resource
+ */
+ getResourceType: function () {
+ return this.nativeResource.resourceType;
+ },
+
+ /**
+ * Resolve a path to a resource. The path may be relative
+ * to this path
+ * @param {string} path the requested path
+ * @return {promise.<Resource>} the promise of a resource. If the resource
+ * does not exist, the promise will fail
+ */
+ resolve: function(path) {
+ var resolver = this.nativeResource.getResourceResolver();
+ var res = resolver.getResource(this.nativeResource, path);
+ if (res == null) {
+ return this._promise.failure(new Error('No resource found at path: ' + path));
+ }
+ return this._promise.success(new Resource(res, this._promise));
+ }
+ };
+
+ Object.defineProperties(Resource.prototype, {
+ /**
+ * The name of the resource
+ * @name Resource~name
+ * @member
+ * @type {string}
+ */
+ name: {
+ get: function() {
+ return this.getName();
+ }
+ },
+
+ /**
+ * The resource type
+ * @name Resource~resourceType
+ * @member
+ * @type {string}
+ */
+ resourceType: {
+ get: function() {
+ return this.getResourceType();
+ }
+ }
+ });
+
+ return Resource;
+
+});
diff --git a/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js
new file mode 100644
index 0000000..82b6770
--- /dev/null
+++ b/src/main/resources/SLING-INF/libs/sling/sightly/js/internal/sly.js
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+use(['resource.js', 'request.js', 'promise.js'], function(Resource, Request, promiseFactory) {
+
+ return function(bindings, Q) {
+ var promiseLib = promiseFactory(Q);
+ var slyResource;
+ var slyProperties;
+ var slyRequest;
+ if (bindings.containsKey('resource')) {
+ slyResource = new Resource(bindings.get('resource'), promiseLib);
+ slyProperties = slyResource.properties;
+ }
+ if (bindings.containsKey('request')) {
+ slyRequest = new Request(bindings.get('request'));
+ }
+
+ /**
+ * @namespace sly
+ */
+ return /** @lends sly */ {
+
+ /**
+ * The current resource of the request
+ * @type {Resource}
+ */
+ resource: slyResource,
+
+ /**
+ * The properties of the current resource
+ * @type {Object.<string, Object>}
+ */
+ properties: slyResource.properties,
+
+ /**
+ * The request object
+ * @type {Request}
+ */
+ request: slyRequest
+ };
+ }
+
+});
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.