Move Sling to new TLP location
git-svn-id: https://svn.eu.apache.org/repos/asf/sling/tags/org.apache.sling.scripting.javascript-2.0.2-incubator@785979 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..39edd96
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+Apache Sling JavaScript (Rhino) Scripting Support
+Copyright 2008 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..1615682
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,37 @@
+Apache Sling JavaScript (Rhino) Scripting Support
+
+Support for JavaScript scripting.
+
+Disclaimer
+==========
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+ mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+ svn checkout http://svn.apache.org/repos/asf/incubator/sling/trunk/scripting/javascript
+
+See the Subversion documentation for other source control features.
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..47ea509
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>3-incubator</version>
+ <relativePath>../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.scripting.javascript</artifactId>
+ <version>2.0.2-incubator</version>
+ <packaging>bundle</packaging>
+
+ <name>Sling - Scripting - JavaScript Support</name>
+ <description>Support for JavaScript scripting</description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.scripting.javascript-2.0.2-incubator</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.scripting.javascript-2.0.2-incubator</developerConnection>
+ <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.scripting.javascript-2.0.2-incubator</url>
+ </scm>
+
+ <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>
+ <Private-Package>
+ org.apache.sling.scripting.javascript.*,
+ org.mozilla.classfile,
+ !org.mozilla.javascript.xml.impl.xmlbeans,
+ org.mozilla.javascript.*
+ </Private-Package>
+ <DynamicImport-Package>*</DynamicImport-Package>
+ <ScriptEngine-Name>${pom.name}</ScriptEngine-Name>
+ <ScriptEngine-Version>${pom.version}</ScriptEngine-Version>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!-- No javadocs -->
+ <excludePackageNames>
+ org.apache.sling.scripting.javascript
+ </excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.0.2-incubator</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.scripting.api</artifactId>
+ <version>2.0.2-incubator</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.jcr.resource</artifactId>
+ <version>2.0.2-incubator</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.testing</artifactId>
+ <version>2.0.2-incubator</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.json</artifactId>
+ <version>2.0.2-incubator</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>rhino</groupId>
+ <artifactId>js</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <!-- Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngine.java b/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngine.java
new file mode 100644
index 0000000..523a50f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngine.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript;
+
+import java.io.Reader;
+import java.util.Map.Entry;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptException;
+
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
+import org.apache.sling.scripting.javascript.helper.SlingWrapFactory;
+import org.apache.sling.scripting.javascript.io.EspReader;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.JavaScriptException;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.slf4j.Logger;
+
+/**
+ * A ScriptEngine that uses the Rhino interpreter to process Sling requests with
+ * server-side javascript.
+ */
+public class RhinoJavaScriptEngine extends AbstractSlingScriptEngine {
+
+ private Scriptable rootScope;
+
+ public RhinoJavaScriptEngine(ScriptEngineFactory factory, Scriptable rootScope) {
+ super(factory);
+ this.rootScope = rootScope;
+ }
+
+ public Object eval(Reader scriptReader, ScriptContext scriptContext)
+ throws ScriptException {
+ Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+ String scriptName = "NO_SCRIPT_NAME";
+ {
+ SlingScriptHelper helper = (SlingScriptHelper) bindings.get(SlingBindings.SLING);
+ if(helper != null) {
+ scriptName = helper.getScript().getScriptResource().getPath();
+ }
+ }
+
+ // wrap the reader in an EspReader for ESP scripts
+ if (scriptName.endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) {
+ scriptReader = new EspReader(scriptReader);
+ }
+
+ // create a rhino Context and execute the script
+ try {
+
+ final Context rhinoContext = Context.enter();
+ final ScriptableObject scope = new NativeObject();
+
+ // Set the global scope to be our prototype
+ scope.setPrototype(rootScope);
+
+ // We want "scope" to be a new top-level scope, so set its parent
+ // scope to null. This means that any variables created by assignments
+ // will be properties of "scope".
+ scope.setParentScope(null);
+
+ // setup the context for use
+ rhinoContext.setWrapFactory(SlingWrapFactory.INSTANCE);
+
+ // add initial properties to the scope
+ for (Object entryObject : bindings.entrySet()) {
+ Entry<?, ?> entry = (Entry<?, ?>) entryObject;
+ Object wrapped = ScriptRuntime.toObject(scope, entry.getValue());
+ ScriptableObject.putProperty(scope, (String) entry.getKey(),
+ wrapped);
+ }
+
+ final int lineNumber = 1;
+ final Object securityDomain = null;
+
+ return rhinoContext.evaluateReader(scope, scriptReader, scriptName,
+ lineNumber, securityDomain);
+
+ } catch (JavaScriptException t) {
+
+ final ScriptException se = new ScriptException(t.details(),
+ t.sourceName(), t.lineNumber());
+
+ ((Logger) bindings.get(SlingBindings.LOG)).error(t.getScriptStackTrace());
+ se.setStackTrace(t.getStackTrace());
+ throw se;
+
+ } catch (Throwable t) {
+
+ final ScriptException se = new ScriptException("Failure running script " + scriptName
+ + ": " + t.getMessage());
+ se.initCause(t);
+ throw se;
+
+ } finally {
+
+ Context.exit();
+
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngineFactory.java
new file mode 100644
index 0000000..7740ad3
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/RhinoJavaScriptEngineFactory.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript;
+
+import javax.script.ScriptEngine;
+
+import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
+import org.apache.sling.scripting.javascript.helper.SlingContextFactory;
+import org.apache.sling.scripting.javascript.helper.SlingWrapFactory;
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableCalendar;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableItemMap;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableNode;
+import org.apache.sling.scripting.javascript.wrapper.ScriptablePrintWriter;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableProperty;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableResource;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableVersion;
+import org.apache.sling.scripting.javascript.wrapper.ScriptableVersionHistory;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.tools.debugger.ScopeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>RhinoJavaScriptEngineFactory</code> TODO
+ */
+public class RhinoJavaScriptEngineFactory extends AbstractScriptEngineFactory
+ implements ScopeProvider {
+
+ public final static String ECMA_SCRIPT_EXTENSION = "ecma";
+
+ public final static String ESP_SCRIPT_EXTENSION = "esp";
+
+ private static final Class<?>[] HOSTOBJECT_CLASSES = {
+ ScriptableResource.class,
+ ScriptableNode.class,
+ ScriptableProperty.class,
+ ScriptableItemMap.class,
+ ScriptablePrintWriter.class,
+ ScriptableVersionHistory.class,
+ ScriptableVersion.class,
+ ScriptableCalendar.class
+ };
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final String languageVersion;
+
+ private Scriptable rootScope;
+
+ public RhinoJavaScriptEngineFactory() {
+
+ // initialize the Rhino Context Factory
+ SlingContextFactory.setup(this);
+
+ Context cx = Context.enter();
+ setEngineName(getEngineName() + " (" + cx.getImplementationVersion()
+ + ")");
+ languageVersion = String.valueOf(cx.getLanguageVersion());
+ Context.exit();
+
+ setExtensions(ECMA_SCRIPT_EXTENSION, ESP_SCRIPT_EXTENSION);
+ setMimeTypes("text/javascript", "application/ecmascript",
+ "application/javascript");
+ setNames("javascript", ECMA_SCRIPT_EXTENSION, ESP_SCRIPT_EXTENSION);
+ }
+
+ public ScriptEngine getScriptEngine() {
+ return new RhinoJavaScriptEngine(this, getRootScope());
+ }
+
+ public String getLanguageName() {
+ return "ECMAScript";
+ }
+
+ public String getLanguageVersion() {
+ return languageVersion;
+ }
+
+ public Scriptable getScope() {
+ return getRootScope();
+ }
+
+ private Scriptable getRootScope() {
+ if (rootScope == null) {
+ final Context rhinoContext = Context.enter();
+ rootScope = rhinoContext.initStandardObjects();
+
+ for (Class<?> clazz : HOSTOBJECT_CLASSES) {
+ try {
+
+ // register the host object
+ ScriptableObject.defineClass(rootScope, clazz);
+ final ScriptableObject host = (ScriptableObject) clazz.newInstance();
+
+ if (SlingWrapper.class.isAssignableFrom(clazz)) {
+ // SlingWrappers can map to several classes if needed
+ final SlingWrapper hostWrapper = (SlingWrapper) host;
+ for (Class<?> c : hostWrapper.getWrappedClasses()) {
+ SlingWrapFactory.INSTANCE.registerWrapper(c,
+ hostWrapper.getClassName());
+ }
+ } else {
+ // but other ScriptableObjects need to be registered as
+ // well
+ SlingWrapFactory.INSTANCE.registerWrapper(
+ host.getClass(), host.getClassName());
+ }
+ } catch (Throwable t) {
+ log.warn("getRootScope: Cannot prepare host object " + clazz, t);
+ }
+ }
+ }
+
+ return rootScope;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContext.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContext.java
new file mode 100644
index 0000000..1272894
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContext.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.javascript.helper;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScriptableObject;
+
+/**
+ * The <code>SlingContext</code> extends Context to overwrite the
+ * {@link #initStandardObjects(ScriptableObject, boolean)} method to add more
+ * standard objects.
+ */
+public class SlingContext extends Context {
+
+ @Override
+ public ScriptableObject initStandardObjects(ScriptableObject scope,
+ boolean sealed) {
+ ScriptableObject rootScope = super.initStandardObjects(scope, sealed);
+
+ // add Sling global objects
+ SlingGlobal.init(rootScope, sealed);
+
+ return rootScope;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContextFactory.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContextFactory.java
new file mode 100644
index 0000000..46b4b05
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingContextFactory.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.javascript.helper;
+
+import java.io.File;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.tools.debugger.ScopeProvider;
+
+/**
+ * The <code>SlingContextFactory</code> extends the standard Rhino
+ * ContextFactory to provide customized settings, such as having the dynamic
+ * scope feature enabled by default. Other functionality, which may be added
+ * would be something like a configurable maximum script runtime value.
+ */
+public class SlingContextFactory extends ContextFactory {
+
+ private SlingRhinoDebugger debugger;
+ private ScopeProvider scopeProvider;
+ private boolean debuggerActive;
+
+ // conditionally setup the global ContextFactory to be ours. If
+ // a global context factory has already been set, we have lost
+ // and cannot set this one.
+ public static void setup(ScopeProvider sp) {
+ // TODO what do we do in the other case? debugger won't work
+ if (!hasExplicitGlobal()) {
+ initGlobal(new SlingContextFactory(sp));
+ }
+ }
+
+ // private as instances of this class are only used by setup()
+ private SlingContextFactory(ScopeProvider sp)
+ {
+ scopeProvider = sp;
+
+ // TODO make this configurable via OSGi
+ File f = new File("/tmp/sling.debug");
+ debuggerActive = f.exists();
+ }
+
+ @Override
+ protected Context makeContext() {
+ return new SlingContext();
+ }
+
+ @Override
+ protected boolean hasFeature(Context cx, int featureIndex) {
+ if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) {
+ return true;
+ }
+
+ return super.hasFeature(cx, featureIndex);
+ }
+
+ @Override
+ protected void onContextCreated(Context cx) {
+ super.onContextCreated(cx);
+ initDebugger(cx);
+ }
+
+ private void initDebugger(Context cx) {
+ if(!debuggerActive) {
+ return;
+ }
+ try {
+ if (debugger == null) {
+ debugger = new SlingRhinoDebugger(getClass().getSimpleName());
+ debugger.setScopeProvider(scopeProvider);
+ debugger.attachTo(this);
+ }
+ } catch (Exception e) {
+ // TODO log
+ System.err.println("SlingContextFactory.initDebugger(): " + e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingGlobal.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingGlobal.java
new file mode 100644
index 0000000..e15d246
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingGlobal.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.helper;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Serializable;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.scripting.javascript.RhinoJavaScriptEngineFactory;
+import org.apache.sling.scripting.javascript.io.EspReader;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.IdFunctionCall;
+import org.mozilla.javascript.IdFunctionObject;
+import org.mozilla.javascript.Kit;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Wrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>SlingGlobal</code> class provides two interesting new global
+ * functions which are not part of the ECMAScript standard but which are
+ * available in the Rhino Shell and which may be of use by JavaScripts:
+ * <p>
+ * <dl>
+ * <dt><code>print(args, ...)</code></dt>
+ * <dd>Prints the arguments <code>args</code> in a single message to the
+ * scripts logger available as the global <em>log</em> variable.</dd>
+ * <dt><code>load(args, ...)</code></dt>
+ * <dd>Loads the scripts named as parameters into the current scope one, after
+ * the other. Usually the script files are read as plain JavaScript files. If
+ * the file extension happens to be <em>.esp</em> to indicate an ECMAScript
+ * Server Page, the file is read through an
+ * {@link org.apache.sling.scripting.javascript.io.EspReader}. Failure to read
+ * one of the files throws an error.</dd>
+ * </dl>
+ */
+public class SlingGlobal implements Serializable, IdFunctionCall {
+ static final long serialVersionUID = 6080442165748707530L;
+
+ private static final Object FTAG = new Object();
+
+ private static final int Id_load = 1;
+
+ private static final int Id_print = 2;
+
+ private static final int LAST_SCOPE_FUNCTION_ID = 2;
+
+ /** default log */
+ private final Logger defaultLog = LoggerFactory.getLogger(getClass());
+
+ public static void init(Scriptable scope, boolean sealed) {
+ SlingGlobal obj = new SlingGlobal();
+
+ for (int id = 1; id <= LAST_SCOPE_FUNCTION_ID; ++id) {
+ String name;
+ int arity = 1;
+ switch (id) {
+ case Id_load:
+ name = "load";
+ break;
+ case Id_print:
+ name = "print";
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ IdFunctionObject f = new IdFunctionObject(obj, FTAG, id, name,
+ arity, scope);
+ if (sealed) {
+ f.sealObject();
+ }
+ f.exportAsScopeProperty();
+ }
+
+ }
+
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args) {
+ if (f.hasTag(FTAG)) {
+ int methodId = f.methodId();
+ switch (methodId) {
+ case Id_load: {
+ load(cx, thisObj, args);
+ return Context.getUndefinedValue();
+ }
+
+ case Id_print: {
+ print(cx, thisObj, args);
+ return Context.getUndefinedValue();
+ }
+ }
+ }
+ throw f.unknown();
+ }
+
+ private void print(Context cx, Scriptable thisObj, Object[] args) {
+ StringBuffer message = new StringBuffer();
+ for (int i = 0; i < args.length; i++) {
+ if (i > 0) {
+ message.append(" ");
+ }
+ // Convert the arbitrary JavaScript value into a string form.
+ String s = ScriptRuntime.toString(args[i]);
+
+ message.append(s);
+ }
+
+ getLogger(cx, thisObj).info(message.toString());
+ }
+
+ private void load(Context cx, Scriptable thisObj, Object[] args) {
+
+ SlingScriptHelper sling = getProperty(cx, thisObj, SlingBindings.SLING,
+ SlingScriptHelper.class);
+ if (sling == null) {
+ throw new NullPointerException(SlingBindings.SLING);
+ }
+
+ Scriptable globalScope = ScriptableObject.getTopLevelScope(thisObj);
+
+ Resource scriptResource = sling.getScript().getScriptResource();
+ ResourceResolver resolver = scriptResource.getResourceResolver();
+
+ // the path of the current script to resolve realtive paths
+ String currentScript = sling.getScript().getScriptResource().getPath();
+ String scriptParent = ResourceUtil.getParent(currentScript);
+
+ for (Object arg : args) {
+ String scriptName = ScriptRuntime.toString(arg);
+
+ Resource loadScript = null;
+ if (!scriptName.startsWith("/")) {
+ String absScriptName = scriptParent + "/" + scriptName;
+ loadScript = resolver.resolve(absScriptName);
+ }
+
+ // not resolved relative to the current script
+ if (loadScript == null) {
+ loadScript = resolver.resolve(scriptName);
+ }
+
+ if (loadScript == null) {
+ throw Context.reportRuntimeError("Script file " + scriptName
+ + " not found");
+ }
+
+ InputStream scriptStream = loadScript.adaptTo(InputStream.class);
+ if (scriptStream == null) {
+ throw Context.reportRuntimeError("Script file " + scriptName
+ + " cannot be read from");
+ }
+
+ try {
+ // reader for the stream
+ Reader scriptReader = new InputStreamReader(scriptStream);
+
+ // check whether we have to wrap the basic reader
+ if (scriptName.endsWith(RhinoJavaScriptEngineFactory.ESP_SCRIPT_EXTENSION)) {
+ scriptReader = new EspReader(scriptReader);
+ }
+
+ // read the suff buffered for better performance
+ scriptReader = new BufferedReader(scriptReader);
+
+ // now, let's go
+ cx.evaluateReader(globalScope, scriptReader, scriptName, 1,
+ null);
+
+ } catch (IOException ioe) {
+
+ throw Context.reportRuntimeError("Failure reading file "
+ + scriptName + ": " + ioe);
+
+ } finally {
+ // ensure the script input stream is closed
+ try {
+ scriptStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the script logger or the logger of this class as a fallback
+ * default if the global log variable is not accessible.
+ */
+ private Logger getLogger(Context cx, Scriptable scope) {
+ Logger log = getProperty(cx, scope, SlingBindings.LOG, Logger.class);
+ if (log == null) {
+ log = this.defaultLog;
+ }
+ return log;
+ }
+
+ /**
+ * Returns the named toplevel property converted to the requested
+ * <code>type</code> or <code>null</code> if no such property exists or
+ * the property is of the wrong type.
+ */
+ @SuppressWarnings("unchecked")
+ private <Type> Type getProperty(Context cx, Scriptable scope, String name,
+ Class<Type> type) {
+ Object prop = ScriptRuntime.name(cx, scope, name);
+
+ if (prop instanceof Wrapper) {
+ prop = ((Wrapper) prop).unwrap();
+ }
+
+ if (type.isInstance(prop)) {
+ return (Type) prop; // unchecked case
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingRhinoDebugger.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingRhinoDebugger.java
new file mode 100644
index 0000000..d24e631
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingRhinoDebugger.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript.helper;
+
+import org.mozilla.javascript.tools.debugger.Dim;
+import org.mozilla.javascript.tools.debugger.SwingGui;
+
+class SlingRhinoDebugger extends Dim {
+ SlingRhinoDebugger(String windowTitle) {
+ final SwingGui gui = new SwingGui(this, windowTitle);
+ gui.pack();
+ gui.setVisible(true);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapFactory.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapFactory.java
new file mode 100644
index 0000000..c23c45d
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.helper;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.WrapFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SlingWrapFactory extends WrapFactory {
+
+ public static final SlingWrapFactory INSTANCE = new SlingWrapFactory();
+
+ /** List of classes that must not be wrapped (added for SLING-382) */
+ private static final Class<?>[] EXCLUDED_CLASSES = {};
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private Map<Class<?>, String> wrappers = new HashMap<Class<?>, String>();
+
+ /**
+ * @param cx the current Context for this thread
+ * @param scope the scope of the executing script
+ * @param javaObject the object to be wrapped
+ * @param staticType type hint. If security restrictions prevent to wrap
+ * object based on its class, staticType will be used instead.
+ * @return the wrapped value which shall not be null
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Scriptable wrapAsJavaObject(Context cx, Scriptable scope,
+ Object javaObject, Class staticType) {
+
+ Scriptable result = null;
+ try {
+ String hostObjectName = getHostObjectName(staticType);
+
+ if (hostObjectName == null) {
+ hostObjectName = getHostObjectName(javaObject.getClass());
+ }
+
+ if (hostObjectName != null) {
+ result = cx.newObject(scope, hostObjectName,
+ new Object[] { javaObject });
+ }
+ } catch (Exception e) {
+ log.warn("Cannot Wrap " + javaObject, e);
+ }
+
+ if(result==null) {
+ result = super.wrapAsJavaObject(cx, scope, javaObject, staticType);
+ }
+
+ return result;
+ }
+
+ private String getHostObjectName(Class<?> javaClass) {
+ if(javaClass==null || isExcluded(javaClass)) {
+ return null;
+ }
+ String hostObjectName = wrappers.get(javaClass);
+ if (hostObjectName == null) {
+ // before SLING-383 the superclass was tested first,
+ // but for Version and VersionHistory this would get
+ // a Node wrapper, that's not what we want
+ final Class<?>[] javaInterfaces = javaClass.getInterfaces();
+ for (int i = 0; i < javaInterfaces.length && hostObjectName == null; i++) {
+ hostObjectName = getHostObjectName(javaInterfaces[i]);
+ }
+
+ if (hostObjectName == null) {
+ hostObjectName = getHostObjectName(javaClass.getSuperclass());
+ }
+ }
+
+ return hostObjectName;
+ }
+
+ /*
+ * Is this class in the excluded class list?
+ */
+ private boolean isExcluded(Class<?> javaClass) {
+ for (Class<?> type : EXCLUDED_CLASSES) {
+ if (type.isAssignableFrom(javaClass)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void registerWrapper(Class<?> javaClass, String hostObjectName) {
+ wrappers.put(javaClass, hostObjectName);
+ }
+
+ public void unregisterWrapper(Class<?> javaClass) {
+ wrappers.remove(javaClass);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapper.java b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapper.java
new file mode 100644
index 0000000..4c4edec
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/helper/SlingWrapper.java
@@ -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.
+ */
+package org.apache.sling.scripting.javascript.helper;
+
+import org.mozilla.javascript.Wrapper;
+
+/** Wrapper with an additional methods to indicate the wrapped classes */
+public interface SlingWrapper extends Wrapper {
+
+ /**
+ * The name of the JavaScript host object "class"
+ */
+ String getClassName();
+
+ /**
+ * The list of Java classes wrapped by this wrapper
+ */
+ Class<?> [] getWrappedClasses();
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/scripting/javascript/io/EspReader.java b/src/main/java/org/apache/sling/scripting/javascript/io/EspReader.java
new file mode 100644
index 0000000..c8a57f7
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/io/EspReader.java
@@ -0,0 +1,802 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.io;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>EspReader</code> is a <code>FilterReader</code> which takes
+ * JSP like input and produces plain ECMA script output. The filtering
+ * modifications done on the input comprise the following :
+ * <ul>
+ * <li>Template text (HTML) is wrapped by out.write(). At most one line of
+ * text is wrapped into a single write() call. Double quote characters in the
+ * template text (e.g. for HTML tag attribute values) are escaped.
+ * <li>ECMA code is written to the output as is.
+ * <li>ECMA slash star (/*) comments are also written as is.
+ * <li>ECMA slash slash (//) comments are written as is.
+ * <li>JSP style template comments (<%-- -->) are also removed from the
+ * stream. Lineendings (LFs and CRLFs) are written, though.
+ * <li>HTML comments (<!-- -->) are not treated specially. Rather they are
+ * handled as plain template text written to the output wrapped in
+ * out.write(). The consequence of this behavious is, that as in JSP ECMA
+ * expressions may be included within the comments.
+ * </ul>
+ * <p>
+ * The nice thing about this reader is, that the line numbers of the resulting
+ * stream match the line numbers of the matching contents of the input stream.
+ * Due to the insertion of write() calls, column numbers will not necessarily
+ * match, though. This is especially true if you mix ECMA code tags (<% %>)
+ * with template text on the same line.
+ * <p>
+ * For maximum performance it is advisable to not create the EspReader with a
+ * plain FileReader or InputStreamReader but rather with a BufferedReader based
+ * on one of the simpler Readers. The reasons for this is, that we call the base
+ * reader character by character. This in turn is not too performing if the base
+ * reader does not buffer its input.
+ */
+public class EspReader extends FilterReader {
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(EspReader.class);
+
+ /**
+ * Default parser state. This is the state the parser starts running in. In
+ * this state all text is treated as template text, which should be wrapped
+ * by out.write() line by line.
+ */
+ private static final byte PARSE_STATE_ESP = 1;
+
+ /**
+ * ECMA script reading state. When in this state everything upto to the next
+ * <code>%></code> is written to the output verbatim with three
+ * exceptions : ECMA slash star comments are handed over to handled by the
+ * {@link #PARSE_STATE_ECMA_COMMENT} state, quoted strings are handled in
+ * the {@link #PARSE_STATE_QUOTE} state and ECMA slash slash comments are
+ * handled in {@link #PARSE_STATE_ECMA_COMMENTL} state.
+ */
+ private static final byte PARSE_STATE_ECMA = 2;
+
+ /**
+ * ECMA script expression reading state. This state works exactly the same
+ * as the {@link #PARSE_STATE_ECMA} state with one exception: The whole
+ * code enclosed in the <code><%=</code> ... <code>%></code> tags
+ * is itself wrapped with a <code>out.write()</code> statement
+ * verbatim.
+ */
+ private static final byte PARSE_STATE_ECMA_EXPR = 3;
+
+ /**
+ * Compact ESP expression syntax similar to JSP Expression Language notation
+ */
+ private static final byte PARSE_STATE_ECMA_EXPR_COMPACT = 4;
+
+ /**
+ * JSP comment reading state. When in this state everything upto the closing
+ * <code>--></code> tag is removed from the stream.
+ */
+ private static final byte PARSE_STATE_JSP_COMMENT = 5;
+
+ /**
+ * ECMA quoted string reading state. When in this state everything is
+ * written exactly as in the input stream upto the closing quote, which
+ * matches the opening quote.
+ */
+ private static final byte PARSE_STATE_QUOTE = 6;
+
+ /**
+ * Verbatim copy state. When in this state as many as verbatimChars
+ * characters are returned unchecked. As soon as this number of characters
+ * is returned, the last state is popped from the stack. This state is
+ * mainly used to (re-)inject static text into the output without further
+ * processing.
+ */
+ private static final byte PARSE_STATE_VERBATIM = 7;
+
+ /**
+ * ECMA Comment reading state. When in this state, an ECMA slash star
+ * comment is read (and completely returned).
+ */
+ private static final byte PARSE_STATE_ECMA_COMMENT = 8;
+
+ /**
+ * ECMA Comment reading state. When in this state, an ECMA slash slash
+ * comment is read (and completely returned).
+ */
+ private static final byte PARSE_STATE_ECMA_COMMENTL = 9;
+
+ /**
+ * To work with lookahead and character insertion, we use a PushbackReader.
+ */
+ private PushbackReader input;
+
+ /**
+ * Current parse state. This field contains one of the
+ * <code>PARSE_STATE</code> constants.
+ */
+ private byte state;
+
+ /**
+ * Stack of states. Whenever we enter a new state, the old state is pushed
+ * onto the stack. When a state is left, the previous one is popped from the
+ * stack.
+ *
+ * @see #pushState(byte)
+ * @see #popState()
+ * @see #state
+ */
+ private Stack<Byte> stateStack;
+
+ /**
+ * This value is set to true, if the parser is expected to insert a
+ * out.write() call into the input stream when in state
+ * {@link #PARSE_STATE_ESP}. When this field is true, it is not
+ * necessairily the case, that we are at the start of a real text line.
+ */
+ private boolean lineStart;
+
+ /**
+ * If characters are put into the pushback Stream that should be given back
+ * verbatim, this value is set to the number of such consecutive characters.
+ */
+ private int verbatimChars;
+
+ /**
+ * During String matching this is the character used for string quoting.
+ */
+ private char quoteChar;
+
+ /**
+ * Set to true if an escape character (\) has been encountered within a
+ * quoted string.
+ */
+ private boolean escape;
+
+ /**
+ * Whether the definition of the out variable has already been written or not.
+ * The initial value is <code>true</code> indicating it has still to be
+ * defined.
+ *
+ * @see #startWrite(String)
+ */
+ private boolean outUndefined = true;
+
+ /**
+ * Javascript statement that sets the "out" variable that's used
+ * to output data. Automatically inserted by the reader in code,
+ * where needed.
+ */
+ public static final String DEFAULT_OUT_INIT_STATEMENT = "out=response.writer;";
+ private String outInitStatement = DEFAULT_OUT_INIT_STATEMENT;
+
+ /**
+ * Create an EspReader on top of the given <code>baseReader</code>. The
+ * constructor wraps the input reader with a <code>PushbackReader</code>,
+ * so that input stream modifications may be handled transparently by our
+ * {@link #doRead()} method.
+ */
+ public EspReader(Reader baseReader) {
+ super(baseReader);
+ this.input = new PushbackReader(baseReader, 100);
+ this.stateStack = new Stack<Byte>();
+ this.lineStart = true;
+ this.verbatimChars = -1;
+ this.quoteChar = 0;
+ this.escape = false;
+
+ // Start in ESP (template text) state
+ pushState(PARSE_STATE_ESP);
+ }
+
+ /** Set the code fragment used to initialize the "out" variable */
+ public void setOutInitStatement(String statement) {
+ outInitStatement = statement;
+ }
+
+ /**
+ * Check whether we may block at the next read() operation. We may be ready
+ * if and only if our input reader is ready. But this does not guarantee
+ * that we won't block, as due to filtering there may be more than one
+ * character needed from the input to return one.
+ *
+ * @return <code>true</code> if a character is available on the
+ * <code>PushbackReader</code>.
+ * @throws IOException if the reader is not open
+ */
+ public boolean ready() throws IOException {
+ ensureOpen();
+ return input.ready();
+ }
+
+ /**
+ * Return the next filtered character. This need not be the next character
+ * of the input stream. It may be a character from the input reader, after
+ * having skipped filtered characters or it may be a character injected due
+ * to translation of template text to ECMA code.
+ *
+ * @return the next character after filtering or -1 at the end of the input
+ * reader
+ * @throws IOException if the reader is not open
+ */
+ public int read() throws IOException {
+ ensureOpen();
+ return doRead();
+ }
+
+ /**
+ * Fill the given buffer with filtered or injected characters. This need not
+ * be the next characters of the input stream. It may be characters from the
+ * input reader, after having skipped filtered characters or it may be a
+ * characters injected due to translation of template text to ECMA code.
+ * This method is exactly the same as
+ * <code>read(cbuf, 0, cbuf.length)</code>.
+ *
+ * @param cbuf The character buffer to fill with (filtered) characters
+ * @return the number of characters filled in the buffer or -1 at the end of
+ * the input reader.
+ * @throws IOException if the reader is not open
+ */
+ public int read(char[] cbuf) throws IOException {
+ return read(cbuf, 0, cbuf.length);
+ }
+
+ /**
+ * Fill the buffer from the offset with the number of characters given. This
+ * need not be the next characters of the input stream. It may be characters
+ * from the input reader, after having skipped filtered characters or it may
+ * be a characters injected due to translation of template text to ECMA
+ * code.
+ *
+ * @param cbuf The character buffer to fill with (filtered) characters
+ * @param off Offset from where to start in the buffer
+ * @param len The number of characters to fill into the buffer
+ * @return the number of characters filled in the buffer or -1 at the end of
+ * the input reader.
+ * @throws IOException if the reader is not open
+ * @throws IndexOutOfBoundsException if len is negative, off is negative or
+ * higher than the buffer length or off+len is negative or
+ * beyond the buffer size.
+ */
+ public int read(char[] cbuf, int off, int len) throws java.io.IOException {
+ ensureOpen();
+
+ // Check lines (taken from InputStreamReader ;-)
+ if ((off < 0) || (off > cbuf.length) || (len < 0)
+ || ((off + len) > cbuf.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ int i;
+ for (i = 0; i < len; i++, off++) {
+ int c = doRead();
+ if (c < 0) {
+ break;
+ }
+ cbuf[off] = (char) c;
+ }
+
+ // return EOF (-1) if none have been read, else return the number read
+ return (i == 0) ? -1 : i;
+ }
+
+ /**
+ * Skip the number of filtered characters. The skip method is the same as
+ * calling read() repeatedly for the given number of characters and throwing
+ * away the result. If the end of input reader is reached before having
+ * skipped the number of characters, the method returns the number
+ * characters skipped so far.
+ *
+ * @param n the number of (filtered) characters to skip
+ * @return the number of (filtered) characters actually skipped
+ * @throws IllegalArgumentException if n is negative
+ * @throws IOException if the reading the characters throws
+ */
+ public long skip(long n) throws IOException {
+ if (n < 0L) {
+ throw new IllegalArgumentException("skip value is negative");
+ }
+
+ long i = -1;
+ while (++i < n) {
+ if (doRead() < 0) {
+ break;
+ }
+ }
+ return i;
+ }
+
+ /**
+ * Close the EspReader.
+ */
+ public void close() throws java.io.IOException {
+ if (input != null) {
+ input.close();
+ input = null;
+ }
+
+ // I dont' know what happens ??
+ super.close();
+ }
+
+ /**
+ * Mark the present position in the stream. The <code>mark</code> for
+ * class <code>EspReader</code> always throws an throwable.
+ *
+ * @param readAheadLimit The number of characters to read ahead
+ * @exception IOException Always, since mark is not supported
+ */
+ public void mark(int readAheadLimit) throws IOException {
+ throw new IOException("mark() not supported");
+ }
+
+ /**
+ * Tell whether this stream supports the mark() operation, which it does
+ * not.
+ *
+ * @return false Always, since mark is not supported
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Reset the stream. The <code>reset</code> method of
+ * <code>EspReader</code> always throws an throwable.
+ *
+ * @exception IOException Always, since reset is not supported
+ */
+ public void reset() throws IOException {
+ throw new IOException("reset() not supported");
+ }
+
+ /**
+ * Internal routine doing all the footwork of reading one character at a
+ * time from the <code>PushbackReader</code> and acting according to the
+ * current state.
+ * <p>
+ * This filter is implemented using a finite state machine using the states
+ * defined above with the <code>PARSE_STATE</code> constants. Each state
+ * may do a look ahead in certain situations to decide on further steps.
+ * Characters looked ahead may or may not be inserted back into the input
+ * stream depending on the concrete state.
+ *
+ * @return the next character from the input stream according to the current
+ * state or -1 to indicate end of file.
+ * @throws IOException if the input <code>PushbackReader</code> throws it
+ */
+ private int doRead() throws IOException {
+
+ // we return out of the loop, if we find a character passing the filter
+ for (;;) {
+
+ // Get a character from the input, which may well have been
+ // injected using the unread() method
+ int c = input.read();
+
+ // catch EOF
+ if (c < 0) {
+
+ // if a template text line is still incomplete, inject
+ // proper line ending and continue until this has been returned
+ if (!lineStart && state == PARSE_STATE_ESP) {
+ doVerbatim("\");"); // line ending injection
+ lineStart = true; // mark the line having ended
+ continue; // let's start read the injection
+ }
+
+ return c; // return the marker, we're done
+ }
+
+ // Do the finite state machine
+ switch (state) {
+
+ // NOTE :
+ // - continue means ignore current character, read next
+ // - break means return current character
+
+ // Template text state - text is wrapped in out.write()
+ case PARSE_STATE_ESP:
+ if (c == '$') { // might start EL-like ECMA expr
+ int c2 = input.read();
+ if (c2 == '{') {
+ // ECMA expression ${ ... }
+ pushState(PARSE_STATE_ECMA_EXPR_COMPACT);
+ startWrite(null);
+ if (!lineStart) {
+ doVerbatim("\");");
+ }
+ continue;
+ }
+
+ input.unread(c2);
+
+ } else if (c == '<') { // might start ECMA code/expr, ESP comment or JSP comment
+ int c2 = input.read();
+ int c3 = input.read();
+
+ if (c2 == '%') {
+ // ECMA or JSP comment
+
+ if (c3 == '=') {
+
+ // ECMA expression <%= ... %>
+ pushState(PARSE_STATE_ECMA_EXPR);
+ startWrite(null);
+ if (!lineStart) {
+ doVerbatim("\");");
+ }
+ continue;
+
+ } else if (c3 == '-') {
+
+ // (Possible) JSP Comment <%-- ... --%>
+ int c4 = input.read();
+ if (c4 == '-') {
+ pushState(PARSE_STATE_JSP_COMMENT);
+ continue;
+ }
+ input.unread(c4);
+
+ }
+
+ // We only get here if we are sure about ECMA
+
+ // ECMA code <% ... %>
+ input.unread(c3);
+ pushState(PARSE_STATE_ECMA);
+ if (!lineStart) {
+ doVerbatim("\");");
+ }
+ continue;
+
+ }
+
+ // Nothing special, push back read ahead
+ input.unread(c3);
+ input.unread(c2);
+
+ // End of template text line
+ } else if (c == '\r' || c == '\n') {
+ String lineEnd; // will be injected
+
+ // Check for real CRLF
+ if (c == '\r') {
+ int c2 = input.read();
+ if (c2 != '\n') {
+ input.unread(c2);
+ lineEnd = "\\r";
+ } else {
+ lineEnd = "\\r\\n";
+ }
+ } else {
+ lineEnd = "\\n";
+ }
+
+ // Only write line ending if not empty
+ if (!lineStart) {
+ doVerbatim("\");\n");
+ doVerbatim(lineEnd);
+ lineStart = true;
+
+ } else { // if (lineEnd.length() > 1) {
+ // no matter what line ending we have, make it LF
+ doVerbatim("\");\n");
+ doVerbatim(lineEnd);
+ startWrite("\"");
+ }
+
+ continue;
+
+ // template text is wrapped with double quotes, which
+ // when occurring in the text must be escaped.
+ // We also escape the escape character..
+ } else if (c == '"' || c == '\\') {
+
+ doVerbatim(String.valueOf((char) c));
+ c = '\\';
+
+ }
+
+ // If in template text at the beginning of a line
+ if (lineStart) {
+ lineStart = false;
+ startWrite("\"" + (char) c);
+ continue;
+ }
+
+ break;
+
+ // Reading ECMA code or and ECMA expression
+ case PARSE_STATE_ECMA_EXPR:
+ case PARSE_STATE_ECMA:
+
+ if (c == '%') {
+
+ // might return to PARSE_STATE_ESP
+ int c2 = input.read();
+ if (c2 == '>') {
+
+ // An expression is wrapped in out.write()
+ if (popState() == PARSE_STATE_ECMA_EXPR) {
+ doVerbatim(");");
+ }
+
+ // next ESP needs out.write(
+ lineStart = true;
+
+ continue;
+
+ }
+
+ // false alert, push back
+ input.unread(c2);
+
+ } else if (c == '/') {
+
+ // might be ECMA Comment
+ int c2 = input.read();
+ if (c2 == '/') {
+ // single line comment
+ pushState(PARSE_STATE_ECMA_COMMENTL);
+ } else if (c2 == '*') {
+ // multiline comment
+ pushState(PARSE_STATE_ECMA_COMMENT);
+ }
+
+ // false alert, push back
+ input.unread(c2);
+
+ } else if (c == '\'' || c == '"') {
+
+ // an ECMA string
+ escape = false; // start unescaped
+ quoteChar = (char) c; // to recognize the end
+ pushState(PARSE_STATE_QUOTE);
+
+ }
+ break;
+
+ // reading compact (EL-like) ECMA Expression
+ case PARSE_STATE_ECMA_EXPR_COMPACT:
+ if (c == '}') { //might be the end of a compact expression
+ // An expression is wrapped in out.write()
+ popState();
+ doVerbatim(");");
+
+ // next ESP needs out.write(
+ lineStart = true;
+
+ continue;
+
+ }
+ break;
+
+ // Reading a JSP comment, only returning line endings
+ case PARSE_STATE_JSP_COMMENT:
+
+ // JSP comments end complexly with --%>
+ if (c == '-') {
+ int c2 = input.read();
+ if (c2 == '-') {
+ int c3 = input.read();
+ if (c3 == '%') {
+ int c4 = input.read();
+ if (c4 == '>') {
+
+ // we really reached the end ...
+ popState();
+ continue;
+
+ }
+ input.unread(c4);
+ }
+ input.unread(c3);
+ }
+ input.unread(c2);
+
+ // well, not definitely correct but reasonably accurate
+ // ;-)
+ } else if (c == '\r' || c == '\n') {
+
+ // terminate an open template line
+ if (!lineStart) {
+ input.unread(c); // push back the character
+ doVerbatim("\");"); // insert ");
+ lineStart = true; // mark the line start
+ continue; // Force read of the "
+ }
+
+ break;
+ }
+
+ // continue reading another character in the comment
+ continue;
+
+ // Read an ECMA string upto the ending quote character
+ case PARSE_STATE_QUOTE:
+
+ // if unescaped quote character
+ if (c == quoteChar && !escape) {
+ popState();
+ } else {
+ // mark escape - only if not already escaped (bug 7079)
+ escape = c == '\\' && !escape;
+ }
+
+ break;
+
+ // Return characters unfiltered
+ case PARSE_STATE_VERBATIM:
+
+ // Go back to previous state if all characters read
+ if (--verbatimChars < 0) {
+ popState();
+ }
+
+ break;
+
+ // Return an ECMA multiline comment, ending with */
+ case PARSE_STATE_ECMA_COMMENT:
+
+ // Might be the end of the comment
+ if (c == '*') {
+ int c2 = input.read();
+ if (c2 == '/') {
+ popState(); // back to previous
+ doVerbatim("/"); // return slash verbatim
+ } else {
+ input.unread(c2);
+ }
+ }
+
+ break;
+
+ // Return an ECMA single line comment, ending with end of line
+ case PARSE_STATE_ECMA_COMMENTL:
+
+ // CRLF recognition
+ if (c == '\r') {
+ int c2 = input.read();
+ if (c2 == '\n') {
+ popState();
+ }
+ input.unread(c2);
+
+ // LF only line end
+ } else if (c == '\n') {
+ popState();
+ }
+
+ break;
+
+ // What ???!!!
+ default:
+
+ // we warn and go back to default state
+ log.warn("doRead(): unknown state " + state);
+ state = PARSE_STATE_ESP;
+
+ break;
+
+ } // switch
+
+ // Exiting the switch normally we return the current character
+ return c;
+
+ } // for(;;)
+
+ }
+
+ /**
+ * Throw an IOException if the reader is not open
+ *
+ * @throws IOException if the reader is (already) closed
+ */
+ private void ensureOpen() throws IOException {
+ if (input == null) {
+ throw new IOException("Reader is closed");
+ }
+ }
+
+ /**
+ * Injects the call to write template text and checks whether the global
+ * <em>out</em> variable has also to be defined such that the writer is
+ * acquired on demand.
+ *
+ * @param startString Additional data to be injected as initial argument
+ * to the <em>out.write</em> call written. If <code>null</code> just
+ * the method call is injected.
+ *
+ * @throws IOException if the 'unreading' throws
+ */
+ private void startWrite(String startString) throws IOException {
+
+ // inject the out.write( part and the initial string
+ if (startString != null && startString.length() > 0) {
+ doVerbatim(startString);
+ }
+ doVerbatim("out.write(");
+
+ // if out is not set yet, we also acquire it now setting it
+ // globally
+ if (outUndefined) {
+ doVerbatim(outInitStatement);
+ outUndefined = false;
+ }
+ }
+
+ /**
+ * Injects a string into the input stream, sets the number of characters to
+ * return verbatim and change state. The state change only happens if we are
+ * not in verbatim state already. Else the current string is simply
+ * prepended to the previous inhjection. This is simply a convenience method
+ * ;-)
+ *
+ * @param verbatimString The string to inject into the input stream
+ * @throws IOException if the 'unreading' throws
+ */
+ private void doVerbatim(String verbatimString) throws IOException {
+
+ // Push 'back' into PushbackReader
+ input.unread(verbatimString.toCharArray());
+
+ // Set the number of characters to return verbatim
+ verbatimChars += verbatimString.length();
+
+ // Change state if not already in verbatim state
+ if (state != PARSE_STATE_VERBATIM) {
+ pushState(PARSE_STATE_VERBATIM);
+ }
+ }
+
+ /**
+ * Push the current state on stack and set to <code>newState</code>. This
+ * new state is also returned.
+ *
+ * @param newState the new state to set
+ * @return the new state set according to <code>newState</code>
+ */
+ private byte pushState(byte newState) {
+ stateStack.push(state);
+ return state = newState;
+ }
+
+ /**
+ * Sets the current state to the state stored at the top of the stack. If
+ * the stack is empty prior to this call, the default template text state is
+ * set. The method returns the state prior to setting to the new state.
+ *
+ * @return the state prior to calling this method
+ */
+ private byte popState() {
+ byte oldState = state;
+ state = stateStack.isEmpty() ? PARSE_STATE_ESP : stateStack.pop();
+ return oldState;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableBase.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableBase.java
new file mode 100644
index 0000000..97671ec
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableBase.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.mozilla.javascript.NativeJavaObject;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+/** Base class for Scriptable objects, uses the NativeJavaObject wrapper to provide
+ * default wrapping of methods and properties (SLING-397)
+ */
+public abstract class ScriptableBase extends ScriptableObject {
+
+ private NativeJavaObject njo;
+ private final Set<String> jsMethods = getJsMethodNames();
+
+ public static final String JSFUNC_PREFIX = "jsFunction_";
+
+ protected Object getNative(String name, Scriptable start) {
+ final Object wrapped = getWrappedObject();
+
+ if(wrapped == null) {
+ return Scriptable.NOT_FOUND;
+ }
+
+ if(jsMethods.contains(name)) {
+ return Scriptable.NOT_FOUND;
+ }
+
+ if(njo == null) {
+ synchronized (this) {
+ if(njo == null) {
+ njo = new NativeJavaObject(start, wrapped, getStaticType());
+ }
+ }
+ }
+
+ return njo.get(name, start);
+ }
+
+ /** @return the Java object that we're wrapping, used to create a NativeJavaObject
+ * instance for default wrapping.
+ */
+ protected abstract Object getWrappedObject();
+
+ /** @return the static type to use for NativeJavaObject wrapping */
+ protected abstract Class<?> getStaticType();
+
+ /** Used in testing, to check that the right wrapper is used.
+ * For some reason, defining the method here didn't work, it had to be
+ * defined in all descendant classes.
+ */
+ public abstract Class<?> jsGet_javascriptWrapperClass();
+
+ /** @return the Set of method names that clazz defines, i.e. all public methods
+ * with names that start with jsFunction_ */
+ private Set<String> getJsMethodNames() {
+ final Set<String> result = new HashSet<String>();
+
+ for(Method m : getClass().getMethods()) {
+ if(m.getName().startsWith(JSFUNC_PREFIX)) {
+ result.add(m.getName().substring(JSFUNC_PREFIX.length()));
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableCalendar.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableCalendar.java
new file mode 100644
index 0000000..8e2d57e
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableCalendar.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.Undefined;
+import org.apache.sling.commons.json.jcr.JsonItemWriter;
+
+@SuppressWarnings("serial")
+public class ScriptableCalendar extends ScriptableBase implements SlingWrapper {
+
+ public static final String CLASSNAME = "Calendar";
+ private SimpleDateFormat calendarFormat;
+
+ /** Calendar is a class, not an interface - so we need to enumerate possible implementations here */
+ public static final Class<?> [] WRAPPED_CLASSES = { Calendar.class, GregorianCalendar.class };
+
+ private Calendar calendar;
+
+ public Class<?>[] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ public void jsConstructor(Object o) {
+ this.calendar = (Calendar) o;
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+
+ // builtin javascript properties (jsFunction_ etc.) have priority
+ final Object fromSuperclass = super.get(name, start);
+ if(fromSuperclass != Scriptable.NOT_FOUND) {
+ return fromSuperclass;
+ }
+
+ if(calendar == null) {
+ return Undefined.instance;
+ }
+
+ if("date".equals(name)) {
+ return ScriptRuntime.toObject(this, calendar.getTime());
+ }
+
+ return getNative(name, start);
+ }
+
+ @Override
+ protected Class<?> getStaticType() {
+ return Calendar.class;
+ }
+
+ @Override
+ protected Object getWrappedObject() {
+ return calendar;
+ }
+
+ @Override
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ @Override
+ public String toString() {
+ if (calendarFormat == null) {
+ calendarFormat = new SimpleDateFormat(JsonItemWriter.ECMA_DATE_FORMAT, JsonItemWriter.DATE_FORMAT_LOCALE);
+ }
+ return calendarFormat.format(calendar.getTime());
+ }
+
+ public Object unwrap() {
+ return calendar;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getDefaultValue(Class typeHint) {
+ return toString();
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableItemMap.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableItemMap.java
new file mode 100644
index 0000000..f8e9619
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableItemMap.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Undefined;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ScriptableItemMap extends ScriptableObject {
+
+ public static final String CLASSNAME = "ItemMap";
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private Map<String, Item> items = new LinkedHashMap<String, Item>();
+
+ public ScriptableItemMap() {
+ }
+
+ public void jsConstructor(Object res) {
+ if (res instanceof Iterator) {
+ Iterator<?> itemIterator = (Iterator<?>) res;
+ while (itemIterator.hasNext()) {
+ Item item = (Item) itemIterator.next();
+ try {
+ items.put(item.getName(), item);
+ } catch (RepositoryException re) {
+ log.error("ScriptableItemMap<init>: Cannot get name of item "
+ + item, re);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start) {
+ return getItem(index) != null;
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start) {
+ return items.containsKey(name);
+ }
+
+ @Override
+ public Object get(int index, Scriptable start) {
+ Item item = getItem(index);
+ if (item != null) {
+ return ScriptRuntime.toObject(this, item);
+ }
+
+ return Undefined.instance;
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ // special provision for the "length" property to simulate an array
+ if ("length".equals(name)) {
+ return ScriptRuntime.toNumber(this.items.keySet().size()+"");
+ }
+
+ Item item = items.get(name);
+ Object result = Undefined.instance;
+ if (item != null) {
+ result = ScriptRuntime.toObject(this, item);
+ }
+
+ return result;
+ }
+
+ @Override
+ public Object[] getIds() {
+ return items.keySet().toArray();
+ }
+
+ private Item getItem(int index) {
+ if (index < 0 || index >= items.size()) {
+ return null;
+ }
+
+ Iterator<Item> itemsIter = items.values().iterator();
+ while (itemsIter.hasNext() && index > 0) {
+ itemsIter.next();
+ index--;
+ }
+
+ return itemsIter.hasNext() ? itemsIter.next() : null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableNode.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableNode.java
new file mode 100644
index 0000000..80a641e
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableNode.java
@@ -0,0 +1,432 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.sling.jcr.resource.JcrResourceUtil;
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.Undefined;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A wrapper for JCR nodes that exposes all properties and child nodes as
+ * properties of a Javascript object.
+ */
+@SuppressWarnings("serial")
+public class ScriptableNode extends ScriptableBase implements SlingWrapper {
+
+ public static final String CLASSNAME = "Node";
+ public static final Class<?> [] WRAPPED_CLASSES = { Node.class };
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private Node node;
+
+ public void jsConstructor(Object res) {
+ this.node = (Node) res;
+ }
+
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ public Class<?> [] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ @Override
+ protected Class<?> getStaticType() {
+ return Node.class;
+ }
+
+ @Override
+ protected Object getWrappedObject() {
+ return node;
+ }
+
+ public Object jsFunction_addNode(String path, String primaryType) throws RepositoryException {
+ Node n = null;
+ if(primaryType == null || "undefined".equals(primaryType)) {
+ n = node.addNode(path);
+ } else {
+ n = node.addNode(path, primaryType);
+ }
+
+ final Object result = ScriptRuntime.toObject(this, n);
+ return result;
+ }
+
+ public Object jsFunction_getNode(String path) throws RepositoryException {
+ return ScriptRuntime.toObject(this, node.getNode(path));
+ }
+
+ public Object jsFunction_getChildren() {
+ try {
+ return toScriptableItemMap(node.getNodes());
+ } catch (RepositoryException re) {
+ log.warn("Cannot get children of " + jsFunction_getPath(), re);
+ return toScriptableItemMap(null);
+ }
+ }
+
+ public Object jsFunction_getNodes(String namePattern) {
+ try {
+ NodeIterator iter = null;
+ if(namePattern == null || "undefined".equals(namePattern)) {
+ iter = node.getNodes();
+ } else {
+ iter = node.getNodes(namePattern);
+ }
+ return toScriptableItemMap(iter);
+ } catch (RepositoryException re) {
+ log.warn("Cannot get children of " + jsFunction_getPath() + " with pattern " + namePattern, re);
+ return toScriptableItemMap(null);
+ }
+ }
+
+ public Object jsFunction_getProperties() {
+ try {
+ return toScriptableItemMap(node.getProperties());
+ } catch (RepositoryException re) {
+ log.warn("Cannot get properties of " + jsFunction_getPath(), re);
+ return toScriptableItemMap(null);
+ }
+ }
+
+ public Object jsFunction_getPrimaryItem() {
+ try {
+ return ScriptRuntime.toObject(this, node.getPrimaryItem());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsFunction_getProperty(String name) throws RepositoryException {
+ Object[] args = { node.getProperty(name) };
+ return ScriptRuntime.newObject(Context.getCurrentContext(), this,
+ ScriptableProperty.CLASSNAME, args);
+ }
+
+ public String jsFunction_getUUID() {
+ try {
+ return node.getUUID();
+ } catch (RepositoryException re) {
+ return "";
+ }
+ }
+
+ public int jsFunction_getIndex() {
+ try {
+ return node.getIndex();
+ } catch (RepositoryException re) {
+ return 1;
+ }
+ }
+
+ public Iterator<?> jsFunction_getReferences() {
+ try {
+ return node.getReferences();
+ } catch (RepositoryException re) {
+ return Collections.EMPTY_LIST.iterator();
+ }
+ }
+
+ public Object jsFunction_getPrimaryNodeType() {
+ try {
+ return node.getPrimaryNodeType();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public NodeType[] jsFunction_getMixinNodeTypes() {
+ try {
+ return node.getMixinNodeTypes();
+ } catch (RepositoryException re) {
+ return new NodeType[0];
+ }
+ }
+
+ public Object jsFunction_getDefinition() {
+ try {
+ return node.getDefinition();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public boolean jsFunction_getCheckedOut() {
+ try {
+ return node.isCheckedOut();
+ } catch (RepositoryException re) {
+ return false;
+ }
+ }
+
+ public Object jsFunction_getVersionHistory() {
+ try {
+ return ScriptRuntime.toObject(this, node.getVersionHistory());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsFunction_getBaseVersion() {
+ try {
+ return ScriptRuntime.toObject(this, node.getBaseVersion());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsFunction_getLock() {
+ try {
+ return node.getLock();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public boolean jsFunction_getLocked() {
+ try {
+ return node.isLocked();
+ } catch (RepositoryException re) {
+ return false;
+ }
+ }
+
+ public Object jsFunction_getSession() {
+ try {
+ return node.getSession();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public String jsFunction_getPath() {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ return node.toString();
+ }
+ }
+
+ public String jsFunction_getName() {
+ try {
+ return node.getName();
+ } catch (RepositoryException e) {
+ return node.toString();
+ }
+ }
+
+ public Object jsFunction_getParent() {
+ try {
+ return ScriptRuntime.toObject(this, node.getParent());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public int jsFunction_getDepth() {
+ try {
+ return node.getDepth();
+ } catch (RepositoryException re) {
+ return -1;
+ }
+ }
+
+ public boolean jsFunction_getNew() {
+ return node.isNew();
+ }
+
+ public boolean jsFunction_getModified() {
+ return node.isModified();
+ }
+
+ public void jsFunction_remove() throws RepositoryException {
+ node.remove();
+ }
+
+ public boolean jsFunction_hasNode(String path) throws RepositoryException {
+ return node.hasNode(path);
+ }
+
+ /**
+ * Gets the value of a (Javascript) property or child node. If there is a single single-value
+ * JCR property of this node, return its string value. If there are multiple properties
+ * of the same name or child nodes of the same name, return an array.
+ */
+ @Override
+ public Object get(String name, Scriptable start) {
+
+ // builtin javascript properties (jsFunction_ etc.) have priority
+ final Object fromSuperclass = super.get(name, start);
+ if(fromSuperclass != Scriptable.NOT_FOUND) {
+ return fromSuperclass;
+ }
+
+ if(node == null) {
+ return Undefined.instance;
+ }
+
+ final List<Scriptable> items = new ArrayList<Scriptable>();
+
+ // Add all matching nodes to result
+ try {
+ NodeIterator it = node.getNodes(name);
+ while (it.hasNext()) {
+ items.add(ScriptRuntime.toObject(this, it.nextNode()));
+ }
+ } catch (RepositoryException e) {
+ log.debug("RepositoryException while collecting Node children",e);
+ }
+
+ // Add all matching properties to result
+ try {
+ PropertyIterator it = node.getProperties(name);
+ while (it.hasNext()) {
+ Property prop = it.nextProperty();
+ int type = prop.getType();
+ if (prop.getDefinition().isMultiple()) {
+ Value[] values = prop.getValues();
+ for (int i=0;i<values.length;i++) {
+ items.add(wrap(values[i]));
+ }
+ } else {
+ if (type==PropertyType.REFERENCE) {
+ items.add(ScriptRuntime.toObject(this, prop.getNode()));
+ } else {
+ items.add(wrap(prop.getValue()));
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ log.debug("RepositoryException while collecting Node properties",e);
+ }
+
+ if (items.size()==0) {
+ return getNative(name, start);
+
+ } else if (items.size()==1) {
+ return items.iterator().next();
+
+ } else {
+ NativeArray result = new NativeArray(items.toArray());
+ ScriptRuntime.setObjectProtoAndParent(result, this);
+ return result;
+ }
+ }
+
+ /** Wrap JCR Values in a simple way */
+ private Scriptable wrap(Value value) throws ValueFormatException,
+ IllegalStateException, RepositoryException {
+ return ScriptRuntime.toObject(this, JcrResourceUtil.toJavaObject(value));
+ }
+
+ @Override
+ public Object[] getIds() {
+ Collection<String> ids = new ArrayList<String>();
+ if(node != null) {
+ try {
+ PropertyIterator pit = node.getProperties();
+ while (pit.hasNext()) {
+ ids.add(pit.nextProperty().getName());
+ }
+ } catch (RepositoryException e) {
+ //do nothing, just do not list properties
+ }
+ try {
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ ids.add(nit.nextNode().getName());
+ }
+ } catch (RepositoryException e) {
+ //do nothing, just do not list child nodes
+ }
+ }
+ return ids.toArray();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getDefaultValue(Class typeHint) {
+ try {
+ return node.getPath();
+ } catch(Exception e) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start) {
+ try {
+ // TODO should this take into account our jsFunction_ members?
+ return node.hasProperty(name) || node.hasNode(name);
+ } catch (RepositoryException e) {
+ return false;
+ }
+ }
+
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ return node.toString();
+ }
+ }
+
+ // ---------- Wrapper interface --------------------------------------------
+
+ // returns the wrapped node
+ public Object unwrap() {
+ return node;
+ }
+
+ //---------- Helper -------------------------------------------------------
+
+ private Object toScriptableItemMap(Iterator<?> iter) {
+ Object[] args = (iter != null) ? new Object[] { iter } : null;
+ return ScriptRuntime.newObject(Context.getCurrentContext(), this,
+ ScriptableItemMap.CLASSNAME, args);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptablePrintWriter.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptablePrintWriter.java
new file mode 100644
index 0000000..d1db5b1
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptablePrintWriter.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import java.io.PrintWriter;
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Wrapper;
+
+public class ScriptablePrintWriter extends ScriptableObject implements SlingWrapper {
+
+ public static final String CLASSNAME = "PrintWriter";
+ public static final Class<?> [] WRAPPED_CLASSES = { PrintWriter.class };
+
+ private PrintWriter writer;
+
+ // the locale to use for printf
+ private Locale locale;
+
+ public ScriptablePrintWriter() {
+ }
+
+ public ScriptablePrintWriter(PrintWriter writer) {
+ this.writer = writer;
+ }
+
+ public void jsConstructor(Object res) {
+ this.writer = (PrintWriter) res;
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ public Class<?> [] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ // print args to writer if any
+ // this method supports write(Object)
+ public static void jsFunction_write(Context cx, Scriptable thisObj,
+ Object[] args, Function funObj) {
+ print(thisObj, args);
+ }
+
+ // print args to writer if any
+ // this method supports print(Object)
+ public static void jsFunction_print(Context cx, Scriptable thisObj,
+ Object[] args, Function funObj) {
+ print(thisObj, args);
+ }
+
+ // print a formatted string to the writer. The first arg is used as the
+ // formatter Locale if it is a Locale instance. The next argument is the
+ // format string followed by format arguments.
+ // This method supports printf(Locale, String, Object...) and
+ // printf(String, Object...)
+ public static void jsFunction_printf(Context cx, Scriptable thisObj,
+ Object[] args, Function funObj) {
+
+ if (args.length > 0) {
+
+ // the index of the next argument to consider
+ int nextIdx = 0;
+
+ // the local for printf
+ Locale locale = null;
+
+ if (args[nextIdx] instanceof Locale) {
+
+ // the Locale is the first argument, use it an increment idx
+ locale = (Locale) args[nextIdx];
+ nextIdx++;
+
+ } else {
+
+ // get the per-HTTP request local or the default locale
+ locale = ((ScriptablePrintWriter) thisObj).getLocale();
+ }
+
+ // only continue, if there is at least an other argument
+ // containing the format string
+ if (args.length > nextIdx) {
+
+ // the format string
+ String format = ScriptRuntime.toString(args[nextIdx]);
+
+ // arguments starting after that are formatting arguments
+ nextIdx++;
+ Object[] formatArgs = new Object[args.length - nextIdx];
+ System.arraycopy(args, nextIdx, formatArgs, 0,
+ formatArgs.length);
+
+ // now get the writer and call printf
+ PrintWriter writer = ((ScriptablePrintWriter) thisObj).writer;
+ writer.printf(locale, format, formatArgs);
+ }
+ }
+ }
+
+ // print args to the writer (if any) and append a line feed
+ // this method supports println(Object)
+ public static void jsFunction_println(Context cx, Scriptable thisObj,
+ Object[] args, Function funObj) {
+ print(thisObj, args).println();
+ }
+
+ // ---------- Wrapper interface --------------------------------------------
+
+ // returns the wrapped print writer
+ public Object unwrap() {
+ return writer;
+ }
+
+ // ---------- internal helper ----------------------------------------------
+
+ // print all arguments as strings to the writer
+ private static PrintWriter print(Object thisObj, Object[] args) {
+ PrintWriter writer = ((ScriptablePrintWriter) thisObj).writer;
+ for (Object arg : args) {
+ writer.print(ScriptRuntime.toString(arg));
+ }
+ return writer;
+ }
+
+ // helper method to return the locale to use for this instance:
+ // - if the global scope has a "request" object which is a
+ // HttpServletRequest, we call getLocale() on that object
+ // - Otherwise or if getLocale returns null, we use the platform default
+ private Locale getLocale() {
+ if (locale == null) {
+
+ try {
+ // check whether we have a request object which has the locale
+ Object reqObj = ScriptRuntime.name(Context.getCurrentContext(),
+ this, SlingBindings.REQUEST);
+ if (reqObj instanceof Wrapper) {
+ Object wrapped = ((Wrapper) reqObj).unwrap();
+ if (wrapped instanceof HttpServletRequest) {
+ locale = ((HttpServletRequest) wrapped).getLocale();
+ }
+ }
+ } catch (Exception e) {
+ // ignore any exceptions resulting from this and use default
+ }
+
+ // default, if the no request locale or no request is available
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+
+ }
+
+ return locale;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableProperty.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableProperty.java
new file mode 100644
index 0000000..438cad6
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableProperty.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.Undefined;
+
+/** Wrap a JCR Property as a Scriptable */
+@SuppressWarnings("serial")
+public class ScriptableProperty extends ScriptableBase implements SlingWrapper {
+
+ public static final String CLASSNAME = "Property";
+
+ public static final Class<?>[] WRAPPED_CLASSES = { Property.class };
+
+ private Property property;
+
+ public ScriptableProperty() {
+ }
+
+ public void jsConstructor(Object res) {
+ this.property = (Property) res;
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ public Class<?>[] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ public Object jsGet_value() {
+ try {
+ return property.getValue();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_values() {
+ try {
+ return property.getValues();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_string() {
+ try {
+ return property.getString();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_stream() {
+ try {
+ return property.getStream();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_long() {
+ try {
+ return property.getLong();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_double() {
+ try {
+ return property.getDouble();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_date() {
+ try {
+ return property.getDate();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_boolean() {
+ try {
+ return property.getBoolean();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_node() {
+ try {
+ return ScriptRuntime.toObject(this, property.getValue());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public Object jsGet_length() {
+ try {
+ return property.getLength();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public long[] jsGet_lengths() {
+ try {
+ return property.getLengths();
+ } catch (RepositoryException re) {
+ return new long[0];
+ }
+ }
+
+ public Object jsGet_definition() {
+ try {
+ return property.getDefinition();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public int getType() {
+ try {
+ return property.getType();
+ } catch (RepositoryException re) {
+ return PropertyType.UNDEFINED;
+ }
+ }
+
+ public Object jsGet_session() {
+ try {
+ return property.getSession();
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public String jsGet_path() {
+ try {
+ return property.getPath();
+ } catch (RepositoryException e) {
+ return property.toString();
+ }
+ }
+
+ public String jsGet_name() {
+ try {
+ return property.getName();
+ } catch (RepositoryException e) {
+ return property.toString();
+ }
+ }
+
+ public Object jsGet_parent() {
+ try {
+ return ScriptRuntime.toObject(this, property.getParent());
+ } catch (RepositoryException re) {
+ return Undefined.instance;
+ }
+ }
+
+ public int jsGet_depth() {
+ try {
+ return property.getDepth();
+ } catch (RepositoryException re) {
+ return -1;
+ }
+ }
+
+ public boolean jsGet_new() {
+ return property.isNew();
+ }
+
+ public boolean jsGet_modified() {
+ return property.isModified();
+ }
+
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+
+ public Object jsFunction_valueOf(String hint) {
+ if ("undefined".equals(hint)) {
+
+ try {
+ switch (property.getType()) {
+ case PropertyType.BOOLEAN:
+ return property.getBoolean();
+ case PropertyType.DATE:
+ return property.getDate();
+ case PropertyType.DOUBLE:
+ return property.getDouble();
+ case PropertyType.LONG:
+ return property.getLong();
+ default:
+ return toString();
+ }
+ } catch (RepositoryException re) {
+ // don't care, just return the string value
+ return toString();
+ }
+
+ } else if ("object".equals(hint)) {
+ // return this as a Scriptable :-)
+ return this;
+
+ } else if ("function".equals(hint)) {
+ // cannot return this as a Function
+ return Undefined.instance;
+
+ } else if ("boolean".equals(hint)) {
+ // boolean value
+ try {
+ property.getBoolean();
+ } catch (RepositoryException re) {
+ return false;
+ }
+
+ } else if ("number".equals(hint)) {
+ // numeric value
+ try {
+ property.getDouble();
+ } catch (RepositoryException re) {
+ return 0.0;
+ }
+ }
+
+ // unknown hint or "string"
+ return toString();
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ final Object fromSuperclass = super.get(name, start);
+ if(fromSuperclass != Scriptable.NOT_FOUND) {
+ return fromSuperclass;
+ }
+
+ if(property == null) {
+ return Undefined.instance;
+ }
+
+ return getNative(name, start);
+ }
+
+ public Object jsFunction_toString() {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return property.getValue().getString();
+ } catch (RepositoryException e) {
+ return property.toString();
+ }
+ }
+
+ // ---------- Wrapper interface --------------------------------------------
+
+ public Object unwrap() {
+ return property;
+ }
+
+ @Override
+ protected Class<?> getStaticType() {
+ return Property.class;
+ }
+
+ @Override
+ protected Object getWrappedObject() {
+ return property;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java
new file mode 100644
index 0000000..df6452a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableResource.java
@@ -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.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.scripting.javascript.helper.SlingWrapper;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Undefined;
+import org.mozilla.javascript.Wrapper;
+
+/**
+ * Resource in JavaScript has following signature: [Object] getData(); [Object]
+ * data [Item] getItem(); [Item] item [String] getResourceType(); [String] type
+ * [String] getPath(); [String] path
+ */
+public class ScriptableResource extends ScriptableObject implements SlingWrapper {
+
+ public static final String CLASSNAME = "Resource";
+ public static final Class<?> [] WRAPPED_CLASSES = { Resource.class };
+
+ private Resource resource;
+
+ public ScriptableResource() {
+ }
+
+ public ScriptableResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ public void jsConstructor(Object res) {
+ this.resource = (Resource) res;
+ }
+
+ public Class<?> [] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ public Object jsFunction_getObject() {
+ return toJS(resource.adaptTo(Object.class));
+ }
+
+ public String jsFunction_getResourceType() {
+ return resource.getResourceType();
+ }
+
+ public String jsGet_type() {
+ return this.jsFunction_getResourceType();
+ }
+
+ public String jsFunction_getPath() {
+ return resource.getPath();
+ }
+
+ public String jsGet_path() {
+ return this.jsFunction_getPath();
+ }
+
+ public Object jsFunction_getMetadata() {
+ return toJS(resource.getResourceMetadata());
+ }
+
+ public Object jsGet_meta() {
+ return jsFunction_getMetadata();
+ }
+
+ public Object jsFunction_getResourceResolver() {
+ return toJS(resource.getResourceResolver());
+ }
+
+ public Object jsGet_resourceResolver() {
+ return jsFunction_getResourceResolver();
+ }
+
+ public static Object jsFunction_adaptTo(Context cx, Scriptable thisObj,
+ Object[] args, Function funObj) {
+
+ // get and unwrap the argument
+ Object arg = (args.length > 0) ? args[0] : null;
+ while (arg instanceof Wrapper) {
+ arg = ((Wrapper) arg).unwrap();
+ }
+
+ // try to get the Class object for the argument
+ Class<?> adapter = null;
+ if (arg instanceof Class) {
+
+ adapter = (Class<?>) arg;
+
+ } else if (arg != null && arg != Undefined.instance) {
+
+ // try loading the class from the String
+ String className = ScriptRuntime.toString(arg);
+ try {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null) {
+ loader = thisObj.getClass().getClassLoader();
+ }
+ adapter = Class.forName(className, true, loader);
+ } catch (Exception e) {
+ // TODO: log exception
+ }
+
+ }
+
+ if (adapter != null) {
+ ScriptableResource sr = (ScriptableResource) thisObj;
+ return sr.toJS(sr.resource.adaptTo(adapter));
+ }
+
+ return Undefined.instance;
+ }
+
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getDefaultValue(Class typeHint) {
+ return resource.getPath();
+ }
+
+ public void setResource(Resource entry) {
+ this.resource = entry;
+ }
+
+ // ---------- Wrapper interface --------------------------------------------
+
+ // returns the wrapped resource
+ public Object unwrap() {
+ return resource;
+ }
+
+ //---------- Internal helper ----------------------------------------------
+
+ private Object toJS(Object javaObject) {
+ if (javaObject == null) {
+ return Undefined.instance;
+ }
+
+ return ScriptRuntime.toObject(this, javaObject);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersion.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersion.java
new file mode 100644
index 0000000..27b26f1
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersion.java
@@ -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.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import javax.jcr.version.Version;
+
+/** Scriptable wrapper for the JCR Version class */
+@SuppressWarnings("serial")
+public class ScriptableVersion extends ScriptableNode {
+
+ public static final String CLASSNAME = "Version";
+ public static final Class<?> [] WRAPPED_CLASSES = { Version.class };
+
+ private Version version;
+
+ @Override
+ public void jsConstructor(Object res) {
+ super.jsConstructor(res);
+ version = (Version)res;
+ }
+
+ @Override
+ protected Class<?> getStaticType() {
+ return Version.class;
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ @Override
+ public Class<?>[] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ @Override
+ protected Object getWrappedObject() {
+ return version;
+ }
+
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersionHistory.java b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersionHistory.java
new file mode 100644
index 0000000..ed23f5f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/javascript/wrapper/ScriptableVersionHistory.java
@@ -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.
+ */
+package org.apache.sling.scripting.javascript.wrapper;
+
+import javax.jcr.version.VersionHistory;
+
+/** Scriptable wrapper for the JCR VersionHistory class */
+@SuppressWarnings("serial")
+public class ScriptableVersionHistory extends ScriptableNode {
+
+ public static final String CLASSNAME = "VersionHistory";
+ public static final Class<?> [] WRAPPED_CLASSES = { VersionHistory.class };
+
+ private VersionHistory versionHistory;
+
+ @Override
+ public void jsConstructor(Object res) {
+ super.jsConstructor(res);
+ versionHistory = (VersionHistory)res;
+ }
+
+ @Override
+ protected Class<?> getStaticType() {
+ return VersionHistory.class;
+ }
+
+ @Override
+ public String getClassName() {
+ return CLASSNAME;
+ }
+
+ @Override
+ public Class<?>[] getWrappedClasses() {
+ return WRAPPED_CLASSES;
+ }
+
+ @Override
+ protected Object getWrappedObject() {
+ return versionHistory;
+ }
+
+ public Class<?> jsGet_javascriptWrapperClass() {
+ return getClass();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/DISCLAIMER b/src/main/resources/META-INF/DISCLAIMER
new file mode 100644
index 0000000..90850c2
--- /dev/null
+++ b/src/main/resources/META-INF/DISCLAIMER
@@ -0,0 +1,7 @@
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..884d3e7
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,681 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
+
+APACHE SLING SUBCOMPONENTS:
+
+Apache Sling includes subcomponents with separate copyright notices and
+license terms. Your use of these subcomponents is subject to the terms
+and conditions of the following licenses.
+
+Rhino
+
+ MOZILLA PUBLIC LICENSE
+ Version 1.1
+
+ ---------------
+
+ 1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+ 2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+ 3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+ 4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+ 5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+ 6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+ 7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+ 8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+ 9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+ 10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+ 11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+ 12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+ 13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+ EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (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.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
new file mode 100644
index 0000000..2604e73
--- /dev/null
+++ b/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,11 @@
+Apache Sling JavaScript (Rhino) Scripting Support
+Copyright 2008 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed at
+mozilla.org (http://www.mozilla.org/).
diff --git a/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
new file mode 100644
index 0000000..272fe50
--- /dev/null
+++ b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
@@ -0,0 +1 @@
+org.apache.sling.scripting.javascript.RhinoJavaScriptEngineFactory
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/scripting/RepositoryScriptingTestBase.java b/src/test/java/org/apache/sling/scripting/RepositoryScriptingTestBase.java
new file mode 100644
index 0000000..bef52b3
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/RepositoryScriptingTestBase.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.naming.NamingException;
+
+import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
+
+
+/** Base class for tests which need a Repository
+ * and scripting functionality */
+public class RepositoryScriptingTestBase extends RepositoryTestBase {
+ protected ScriptEngineHelper script;
+ private int counter;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ script = new ScriptEngineHelper();
+ }
+
+ protected Node getNewNode() throws RepositoryException, NamingException {
+ return getTestRootNode().addNode("test-" + (++counter));
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/ScriptEngineHelper.java b/src/test/java/org/apache/sling/scripting/ScriptEngineHelper.java
new file mode 100644
index 0000000..fbdcda3
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/ScriptEngineHelper.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+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.sling.scripting.javascript.RhinoJavaScriptEngineFactory;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Wrapper;
+
+/** Helpers to run javascript code fragments in tests */
+public class ScriptEngineHelper {
+ private static ScriptEngine engine;
+
+ public static class Data extends HashMap<String, Object> {
+ }
+
+ private static ScriptEngine getEngine() {
+ if (engine == null) {
+ synchronized (ScriptEngineHelper.class) {
+ engine = new RhinoJavaScriptEngineFactory().getScriptEngine();
+ }
+ }
+ return engine;
+ }
+
+ public String evalToString(String javascriptCode) throws ScriptException {
+ return evalToString(javascriptCode, null);
+ }
+
+ public Object eval(String javascriptCode, Map<String, Object> data)
+ throws ScriptException {
+ return eval(javascriptCode, data, new StringWriter());
+ }
+
+ public String evalToString(String javascriptCode, Map<String, Object> data)
+ throws ScriptException {
+ final StringWriter sw = new StringWriter();
+ eval(javascriptCode, data, sw);
+ return sw.toString();
+ }
+
+ public Object eval(String javascriptCode, Map<String, Object> data,
+ final StringWriter sw) throws ScriptException {
+ final PrintWriter pw = new PrintWriter(sw, true);
+ ScriptContext ctx = new SimpleScriptContext();
+
+ final Bindings b = new SimpleBindings();
+ b.put("out", pw);
+ if (data != null) {
+ for (Map.Entry<String, Object> e : data.entrySet()) {
+ b.put(e.getKey(), e.getValue());
+ }
+ }
+
+ ctx.setBindings(b, ScriptContext.ENGINE_SCOPE);
+ ctx.setWriter(sw);
+ ctx.setErrorWriter(new OutputStreamWriter(System.err));
+ Object result = getEngine().eval(javascriptCode, ctx);
+
+ if (result instanceof Wrapper) {
+ result = ((Wrapper) result).unwrap();
+ }
+
+ if (result instanceof ScriptableObject) {
+ result = ((ScriptableObject) result).getDefaultValue(null);
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/TestSetupTest.java b/src/test/java/org/apache/sling/scripting/TestSetupTest.java
new file mode 100644
index 0000000..c920141
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/TestSetupTest.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;
+
+
+/** Verify that our test environment works */
+public class TestSetupTest extends RepositoryScriptingTestBase {
+
+ /** Test our test repository setup */
+ public void testRootNode() throws Exception {
+ assertNotNull(getTestRootNode());
+ }
+
+ /** Test our script engine setup */
+ public void testScripting() throws Exception {
+ assertEquals("something",script.evalToString("out.print('something')"));
+ }
+
+ public void testScriptingWithData() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("a", "A");
+ data.put("b", "B");
+ assertEquals("A1",script.evalToString("out.print(a + b.length)", data));
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/javascript/io/EspReaderTest.java b/src/test/java/org/apache/sling/scripting/javascript/io/EspReaderTest.java
new file mode 100644
index 0000000..9bc8f2b
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/javascript/io/EspReaderTest.java
@@ -0,0 +1,300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.scripting.javascript.io;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+import javax.script.ScriptException;
+
+import junit.framework.TestCase;
+
+import org.apache.sling.scripting.ScriptEngineHelper;
+
+/**
+ * The <code>EspReaderTest</code> contains some simple test cases for the
+ * <code>EspReader</code> class which processes ESP (ECMA Server Page) templated
+ * JavaScript and produces plain JavaScript.
+ */
+public class EspReaderTest extends TestCase {
+
+ /** Test read() method */
+ public void testReadSingle() throws IOException {
+ String src = "<%var%>"; // expect var on reader
+
+ Reader reader = new EspReader(new StringReader(src));
+
+ assertTrue("Character 1 must be 'v'", 'v' == reader.read());
+ assertTrue("Character 2 must be 'a'", 'a' == reader.read());
+ assertTrue("Character 3 must be 'r'", 'r' == reader.read());
+ assertTrue("Character 4 must be -1", -1 == reader.read());
+ }
+
+ /** Test read(char[], int, int) method */
+ public void testReadArrayAll() throws IOException {
+ String src = "<%var%>"; // expect var on reader
+
+ Reader reader = new EspReader(new StringReader(src));
+ char[] buf = new char[3];
+ int rd = reader.read(buf, 0, buf.length);
+
+ assertEquals(3, rd);
+ assertEquals("var", new String(buf, 0, rd));
+
+ // nothing more to read, expect EOF
+ rd = reader.read(buf, 0, buf.length);
+ assertEquals(-1, rd);
+ }
+
+ /** Test read(char[], int, int) method */
+ public void testReadArrayOffset() throws IOException {
+ String jsSrc = "var x = 0;";
+ String src = "<%" + jsSrc + "%>";
+
+ Reader reader = new EspReader(new StringReader(src));
+ char[] buf = new char[10];
+ int off = 2;
+ int len = 3;
+ int rd = reader.read(buf, off, len);
+ assertEquals(len, rd);
+ assertEquals("var", new String(buf, off, rd));
+
+ off = 2;
+ len = 7;
+ rd = reader.read(buf, off, len);
+ assertEquals(len, rd);
+ assertEquals(" x = 0;", new String(buf, off, rd));
+
+ // nothing more to read, expect EOF
+ rd = reader.read(buf, 0, buf.length);
+ assertEquals(-1, rd);
+ }
+
+ /** Test standard template text */
+ public void testTemplate() throws IOException {
+ assertEquals("out=response.writer;out.write(\"test\");", parse("test"));
+ assertEquals("out=response.writer;out.write(\"test\\n\");\nout.write(\"test2\");", parse("test\ntest2"));
+ }
+
+ /** Test with a custom "out" initialization */
+ public void testOutInit() throws IOException {
+ final String input = "test";
+ final String expected = "out=getOut();out.write(\"test\");";
+
+ StringBuffer buf = new StringBuffer();
+
+ EspReader r = new EspReader(new StringReader(input));
+ r.setOutInitStatement("out=getOut();");
+ int c;
+ while ( (c=r.read()) >= 0) {
+ buf.append( (char) c);
+ }
+
+ assertEquals(expected, buf.toString());
+ }
+
+ /** Test plain JavaScript code */
+ public void testCode() throws IOException {
+ assertEquals(" test(); ", parse("<% test(); %>"));
+ assertEquals(" \ntest();\ntest2(); ", parse("<% \ntest();\ntest2(); %>"));
+ }
+
+ /** Test JavaScript expressions */
+ public void testExpr() throws IOException {
+ assertEquals("out=response.writer;out.write( x + 1 );", parse("<%= x + 1 %>"));
+ assertEquals("out=response.writer;out.write(\"<!-- \");out.write( x + 1 );out.write(\" -->\");", parse("<!-- <%= x + 1 %> -->"));
+ }
+
+ /** Test JavaScript comment */
+ public void testComment() throws IOException {
+ assertEquals("", parse("<%-- test(); --%>"));
+ }
+
+ public void testCompactExpressionsDouble() throws IOException {
+ final String input = "<html version=\"${1+1}\">\n";
+ final String expected = "out=response.writer;out.write(\"<html version=\\\"\");out.write(1+1);out.write(\"\\\">\\n\");\n";
+ final String actual = parse(input);
+ assertEquals(flatten(expected), flatten(actual));
+ }
+
+ public void testCompactExpressionsDoubleNegative() throws IOException {
+ final String input = "<html version=\"{1+1}\">\n";
+ final String expected = "out=response.writer;out.write(\"<html version=\\\"{1+1}\\\">\\n\");\n";
+ final String actual = parse(input);
+ assertEquals(flatten(expected), flatten(actual));
+ }
+
+ public void testCompactExpressionsSingle() throws IOException {
+ final String input = "<html version='${1+1}'>\n";
+ final String expected = "out=response.writer;out.write(\"<html version='\");out.write(1+1);out.write(\"'>\\n\");\n";
+ final String actual = parse(input);
+ assertEquals(flatten(expected), flatten(actual));
+ }
+
+ public void testCompactExpressionsSingleNegative() throws IOException {
+ final String input = "<html version='{1+1}'>\n";
+ final String expected = "out=response.writer;out.write(\"<html version='{1+1}'>\\n\");\n";
+ final String actual = parse(input);
+ assertEquals(flatten(expected), flatten(actual));
+ }
+
+ /** Test a complete template, using all features */
+ public void testCompleteTemplate() throws IOException {
+ final String input =
+ "<html>\n"
+ + "<head><title><%= someExpr %></title></head>\n"
+ + "<!-- some HTML comment -->\n"
+ + "<-- some ESP comment -->\n"
+ + "// some javascript comment\n"
+ + "/* another javascript comment /*\n"
+ + "<%\n"
+ + "expr on\n"
+ + "two lines\n"
+ + "%>\n"
+ + "<verbatim stuff=\"quoted\">xyz</verbatim>\n"
+ + "<moreverbatim stuff=\'single\'>xx</moreverbatim>\n"
+ + "<!-- HTML comment with <% expr.here; %> and EOL\n-->\n"
+ + "</html>"
+ ;
+
+ final String expected =
+ "out=response.writer;out.write(\"<html>\\n\");\n"
+ + "out.write(\"<head><title>\");out.write( someExpr );out.write(\"</title></head>\\n\");\n"
+ + "out.write(\"<!-- some HTML comment -->\\n\");\n"
+ + "out.write(\"<-- some ESP comment -->\\n\");\n"
+ + "out.write(\"// some javascript comment\\n\");\n"
+ + "out.write(\"/* another javascript comment /*\\n\");\n"
+ + "\n"
+ + "expr on\n"
+ + "two lines\n"
+ + "out.write(\"\\n\");\n"
+ + "out.write(\"<verbatim stuff=\\\"quoted\\\">xyz</verbatim>\\n\");\n"
+ + "out.write(\"<moreverbatim stuff='single'>xx</moreverbatim>\\n\");\n"
+ + "out.write(\"<!-- HTML comment with \"); expr.here; out.write(\" and EOL\\n\");\n"
+ + "out.write(\"-->\\n\");\n"
+ + "out.write(\"</html>\");"
+ ;
+
+ final String actual = parse(input);
+ assertEquals(flatten(expected), flatten(actual));
+ }
+
+ /** Test a complete template, using all features */
+ public void testNumericExpression() throws IOException {
+ String input = "<%= 1 %>";
+ String expected = "out=response.writer;out.write( 1 );";
+ String actual = parse(input);
+ assertEquals(expected, actual);
+
+ input = "<%= \"1\" %>";
+ expected = "out=response.writer;out.write( \"1\" );";
+ actual = parse(input);
+ assertEquals(expected, actual);
+
+ input = "<%= '1' %>";
+ expected = "out=response.writer;out.write( '1' );";
+ actual = parse(input);
+ assertEquals(expected, actual);
+ }
+
+ /** Test a complete template, using all features */
+ public void testNumericExpressionOutput() throws ScriptException {
+ ScriptEngineHelper script = new ScriptEngineHelper();
+
+ String input = "out.write( 1 );";
+ String actual = script.evalToString(input);
+ String expected = "1";
+ assertEquals(expected, actual);
+
+ input = "out.write( \"1\" );";
+ actual = script.evalToString(input);
+ expected = "1";
+ assertEquals(expected, actual);
+
+ input = "out.write( '1' );";
+ actual = script.evalToString(input);
+ expected = "1";
+ assertEquals(expected, actual);
+ }
+
+ public void testColon() throws IOException {
+ final String input = "currentNode.text:<%= currentNode.text %>";
+ final String expected =
+ "out=response.writer;"
+ + "out.write(\"currentNode.text:\");"
+ + "out.write( currentNode.text );"
+ ;
+ final String actual = parse(input);
+ assertEquals(expected, actual);
+ }
+
+ public void testEqualSigns() throws IOException {
+ final String input = "currentNode.text=<%= currentNode.text %>";
+ final String expected =
+ "out=response.writer;"
+ + "out.write(\"currentNode.text=\");"
+ + "out.write( currentNode.text );"
+ ;
+ final String actual = parse(input);
+ assertEquals(expected, actual);
+ }
+
+ public void testSingleQuoted() throws IOException {
+ final String input = "currentNode.text='<%= currentNode.text %>'";
+ final String expected =
+ "out=response.writer;"
+ + "out.write(\"currentNode.text='\");"
+ + "out.write( currentNode.text );"
+ + "out.write(\"'\");"
+ ;
+ final String actual = parse(input);
+ assertEquals(expected, actual);
+ }
+
+ public void testDoubleQuoted() throws IOException {
+ final String input = "currentNode.text=\"<%= currentNode.text %>\"";
+ final String expected =
+ "out=response.writer;"
+ + "out.write(\"currentNode.text=\\\"\");"
+ + "out.write( currentNode.text );"
+ + "out.write(\"\\\"\");"
+ ;
+ final String actual = parse(input);
+ assertEquals(expected, actual);
+ }
+
+ /** Helper to pass an ESP text through the EspReader and return the result */
+ private String parse(String text) throws IOException {
+ StringBuffer buf = new StringBuffer();
+
+ Reader r = new EspReader(new StringReader(text));
+ int c;
+ while ( (c=r.read()) >= 0) {
+ buf.append( (char) c);
+ }
+
+ return buf.toString();
+ }
+
+ /** Replace \n with . in strings to make it easier to compare visually for testing */
+ private static String flatten(String str) {
+ return str.replace('\n', '.');
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/wrapper/ScriptableNodeTest.java b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableNodeTest.java
new file mode 100644
index 0000000..bde9611
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableNodeTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.wrapper;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+
+import org.apache.sling.scripting.RepositoryScriptingTestBase;
+import org.apache.sling.scripting.ScriptEngineHelper;
+import org.apache.sling.commons.json.jcr.JsonItemWriter;
+
+/** Test the ScriptableNode class "live", by retrieving
+ * Nodes from a Repository and executing javascript code
+ * using them.
+ */
+public class ScriptableNodeTest extends RepositoryScriptingTestBase {
+
+ private Node node;
+ private Property textProperty;
+ private String testText;
+ private Property numProperty;
+ private double testNum;
+ private Property calProperty;
+ private Calendar testCal;
+ private ScriptEngineHelper.Data data;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ node = getNewNode();
+ testText = "Test-" + System.currentTimeMillis();
+ node.setProperty("text", testText);
+ node.setProperty("otherProperty", node.getPath());
+
+ testNum = System.currentTimeMillis();
+ node.setProperty("num", testNum);
+
+ testCal = Calendar.getInstance();
+ node.setProperty("cal", testCal);
+
+ data = new ScriptEngineHelper.Data();
+ data.put("node", node);
+ textProperty = node.getProperty("text");
+ data.put("property", textProperty);
+ numProperty = node.getProperty("num");
+ data.put("numProperty", numProperty);
+ calProperty = node.getProperty("cal");
+ data.put("calProperty", calProperty);
+ }
+
+ public void testDefaultValue() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("node", getTestRootNode());
+ assertEquals(
+ getTestRootNode().getPath(),
+ script.evalToString("out.print(node)", data)
+ );
+ }
+
+ public void testPrimaryNodeType() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("node", getTestRootNode());
+ assertEquals(
+ "nt:unstructured",
+ script.evalToString("out.print(node.getPrimaryNodeType().getName())", data)
+ );
+ }
+
+ public void testPrimaryNodeTypeProperty() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("node", getTestRootNode());
+ assertEquals(
+ "nt:unstructured",
+ script.evalToString("out.print(node['jcr:primaryType'])", data)
+ );
+ }
+
+ public void testViaPropertyNoWrappers() throws Exception {
+ assertEquals(
+ testText,
+ script.evalToString("out.print(property.value.string)", data)
+ );
+ }
+
+ public void testViaPropertyWithWrappers() throws Exception {
+ assertEquals(
+ textProperty.getString(),
+ script.evalToString("out.print(property)", data)
+ );
+ }
+
+ public void testViaNodeDirectPropertyAccess() throws Exception {
+ assertEquals(
+ testText,
+ script.evalToString("out.print(node.text)", data)
+ );
+ }
+
+ public void testViaPropertyNoWrappersNum() throws Exception {
+ assertEquals(
+ testNum,
+ script.eval("numProperty.value.getDouble()", data)
+ );
+ }
+
+ public void testViaPropertyWithWrappersNum() throws Exception {
+ assertEquals(
+ testNum,
+ script.eval("0+numProperty", data)
+ );
+ }
+
+ public void testViaNodeDirectPropertyAccessNum() throws Exception {
+ assertEquals(
+ testNum,
+ script.eval("node.num", data)
+ );
+ }
+
+ public void testViaPropertyNoWrappersCal() throws Exception {
+ assertEquals(
+ testCal,
+ script.eval("calProperty.value.getDate()", data)
+ );
+ }
+
+ public void testDateWrapperClass() throws Exception {
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableCalendar",
+ script.eval("node.cal.javascriptWrapperClass.getName()", data)
+ );
+ }
+
+ public void testViaNodeDirectPropertyAccessCal() throws Exception {
+ final SimpleDateFormat f = new SimpleDateFormat(JsonItemWriter.ECMA_DATE_FORMAT, JsonItemWriter.DATE_FORMAT_LOCALE);
+ final String expected = f.format(testCal.getTime());
+ assertEquals(
+ expected,
+ script.evalToString("out.print(node.cal)", data)
+ );
+ }
+
+ public void testCalDateClass() throws Exception {
+ assertEquals(
+ "number",
+ script.evalToString("out.print(typeof node.cal.date.time)", data)
+ );
+ }
+
+ public void testPropertyParent() throws Exception {
+ // need to use node.getProperty('num') to have a ScriptableProperty,
+ // node.num only returns a wrapped value
+ assertEquals(
+ "nt:unstructured",
+ script.eval("node.getProperty('num').parent['jcr:primaryType']", data)
+ );
+ }
+
+ public void testPropertyWrapperClass() throws Exception {
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableProperty",
+ script.eval("node.getProperty('num').javascriptWrapperClass.getName()", data)
+ );
+ }
+
+ public void testPropertyAncestor() throws Exception {
+ // call getAncestor which is not explicitly defined in ScriptableProperty,
+ // to verify that all Property methods are available and that we get a
+ // correctly wrapped result (SLING-397)
+ assertEquals(
+ "rep:root",
+ script.eval("node.getProperty('num').getAncestor(0)['jcr:primaryType']", data)
+ );
+ }
+
+ public void testPropertiesIterationNoWrapper() throws Exception {
+ final String code =
+ "var props = node.getProperties();"
+ + " for(i in props) { out.print(props[i].name); out.print(' '); }"
+ ;
+ final String result = script.evalToString(code, data);
+ final String [] names = { "text", "otherProperty" };
+ for(String name : names) {
+ assertTrue("result (" + result + ") contains '" + name + "'", result.contains(name));
+ }
+ }
+
+ public void testAddNodeDefaultType() throws Exception {
+ final String path = "subdt_" + System.currentTimeMillis();
+ final String code =
+ "var n = node.addNode('" + path + "');\n"
+ + "out.print(n['jcr:primaryType']);\n"
+ ;
+ assertEquals("nt:unstructured", script.evalToString(code, data));
+ }
+
+ public void testAddNodeSpecificType() throws Exception {
+ final String path = "subst_" + System.currentTimeMillis();
+ final String code =
+ "var n = node.addNode('" + path + "', 'nt:folder');\n"
+ + "out.print(n['jcr:primaryType']);\n"
+ ;
+ assertEquals("nt:folder", script.evalToString(code, data));
+ }
+
+ public void testGetNode() throws Exception {
+ final String path = "subgn_" + System.currentTimeMillis();
+ final String code =
+ "node.addNode('" + path + "', 'nt:resource');\n"
+ + "var n=node.getNode('" + path + "');\n"
+ + "out.print(n['jcr:primaryType']);\n"
+ ;
+ assertEquals("nt:resource", script.evalToString(code, data));
+ }
+
+ public void testGetProperty() throws Exception {
+ final String code = "out.print(node.getProperty('text'));";
+ assertEquals(testText, script.evalToString(code, data));
+ }
+
+ public void testGetNodesNoPattern() throws Exception {
+ final String path = "subgnnp_" + System.currentTimeMillis();
+ final String code =
+ "node.addNode('" + path + "_A');\n"
+ + "node.addNode('" + path + "_B');\n"
+ + "var nodes = node.getNodes();\n"
+ + "for (i in nodes) { out.print(nodes[i].getName() + ' '); }\n"
+ ;
+ assertEquals(path + "_A " + path + "_B ", script.evalToString(code, data));
+ }
+
+ public void testGetNodesWithPattern() throws Exception {
+ final String path = "subgnnp_" + System.currentTimeMillis();
+ final String code =
+ "node.addNode('1_" + path + "_A');\n"
+ + "node.addNode('1_" + path + "_B');\n"
+ + "node.addNode('2_" + path + "_C');\n"
+ + "var nodes = node.getNodes('1_*');\n"
+ + "for (i in nodes) { out.print(nodes[i].getName() + ' '); }\n"
+ ;
+ assertEquals("1_" + path + "_A 1_" + path + "_B ", script.evalToString(code, data));
+ }
+
+ public void testRemoveNode() throws Exception {
+ final String code =
+ "node.addNode('toremove');\n"
+ + "out.print(node.hasNode('toremove'))\n"
+ + "out.print(' ')\n"
+ + "node.getNode('toremove').remove()\n"
+ + "out.print(node.hasNode('toremove'))\n"
+ ;
+ assertEquals("true false", script.evalToString(code, data));
+ }
+
+ /** Test SLING-389 */
+ public void testForCurrentNode() throws Exception {
+ final String code = "for (var a in node) {}; out.print('ok')";
+ assertEquals("ok", script.evalToString(code, data));
+ }
+
+ public void testChildNodeAccess() throws Exception {
+ final String path = "subtcna_" + System.currentTimeMillis();
+ final String code =
+ "node.addNode('" + path + "');\n"
+ + "var n=node.getNode('" + path + "');\n"
+ + "out.print(n['jcr:primaryType']);\n"
+ + "out.print(' ');\n"
+ + "var n2=node['" + path + "'];\n"
+ + "out.print(n2['jcr:primaryType']);\n"
+ ;
+ assertEquals("nt:unstructured nt:unstructured", script.evalToString(code, data));
+ }
+
+ /** Verify that the getAncestor() method (which is not explicitely defined in ScriptableNode)
+ * is available, to check SLING-397.
+ */
+ public void testGetAncestor() throws Exception {
+ {
+ final String code = "out.print(node.getAncestor(0).getPath());";
+ assertEquals("/", script.evalToString(code, data));
+ }
+
+ {
+ final String code = "out.print(node.getAncestor(0)['jcr:primaryType']);";
+ assertEquals("rep:root", script.evalToString(code, data));
+ }
+ }
+
+ public void testIsNodeType() throws Exception {
+ final String code =
+ "out.print(node.isNodeType('nt:unstructured'));\n"
+ + "out.print(' ');\n"
+ + "out.print(node.isNodeType('nt:file'));"
+ ;
+ assertEquals("true false", script.evalToString(code, data));
+ }
+
+ public void testNodeWrapperClass() throws Exception {
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableNode",
+ script.eval("node.javascriptWrapperClass.getName()", data)
+ );
+ }
+
+ public void testGetSession() throws Exception {
+ assertEquals(
+ "Root node found via node.session",
+ "/",
+ script.eval("node.session.getRootNode().getPath()", data)
+ );
+ assertEquals(
+ "Root node found via node.getSession()",
+ "/",
+ script.eval("node.getSession().getRootNode().getPath()", data)
+ );
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java
new file mode 100644
index 0000000..6c7ed57
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableResourceTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.wrapper;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.scripting.RepositoryScriptingTestBase;
+import org.apache.sling.scripting.ScriptEngineHelper;
+import org.mozilla.javascript.Undefined;
+import org.mozilla.javascript.Wrapper;
+
+public class ScriptableResourceTest extends RepositoryScriptingTestBase {
+
+ private Node node;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ node = getNewNode();
+ }
+
+ public void testDefaultValuePath() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("resource", new TestResource(node));
+
+ // the path of the resource
+ assertEquals(node.getPath(), script.evalToString("out.print(resource)",
+ data));
+ assertEquals(node.getPath(), script.eval("resource.path", data));
+ assertEquals(node.getPath(), script.eval("resource.getPath()", data));
+ }
+
+ public void testAdaptToNode() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("resource", new TestResource(node));
+
+ // the node to which the resource adapts
+ assertEquals(node, script.eval("resource.adaptTo('javax.jcr.Node')",
+ data));
+ assertEquals(node, script.eval(
+ "resource.adaptTo(Packages.javax.jcr.Node)", data));
+ }
+
+ public void testAdaptToNothing() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("resource", new TestResource(node));
+
+ // the node to which the resource adapts
+ assertEquals(Undefined.instance,
+ script.eval("resource.adaptTo()", data));
+ assertEquals(Undefined.instance, script.eval("resource.adaptTo(null)",
+ data));
+ assertEquals(Undefined.instance, script.eval(
+ "resource.adaptTo(undefined)", data));
+ assertEquals(Undefined.instance, script.eval(
+ "resource.adaptTo(Packages.java.util.Date)", data));
+ }
+
+ public void testResourceWrapperClass() throws Exception {
+ final ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+ data.put("resource", new TestResource(node));
+
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableResource",
+ script.eval("resource.javascriptWrapperClass.getName()", data)
+ );
+ }
+
+ private void assertEquals(Node expected, Object actual) {
+ while (actual instanceof Wrapper) {
+ actual = ((Wrapper) actual).unwrap();
+ }
+
+ super.assertEquals(expected, actual);
+ }
+
+ private static class TestResource implements Resource {
+
+ private final Node node;
+
+ private final String path;
+
+ private final String resourceType;
+
+ private final ResourceMetadata metadata;
+
+ TestResource(Node node) throws RepositoryException {
+ this.node = node;
+ this.path = node.getPath();
+ this.resourceType = node.getPrimaryNodeType().getName();
+ this.metadata = new ResourceMetadata();
+ this.metadata.setResolutionPath(this.path);
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public ResourceMetadata getResourceMetadata() {
+ return metadata;
+ }
+
+ public ResourceResolver getResourceResolver() {
+ // none, don't care
+ return null;
+ }
+
+ public String getResourceType() {
+ return resourceType;
+ }
+
+ public String getResourceSuperType() {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ if (type == Node.class || type == Item.class) {
+ return (AdapterType) node;
+ }
+
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/wrapper/ScriptableVersionTest.java b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableVersionTest.java
new file mode 100644
index 0000000..336f3d7
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/wrapper/ScriptableVersionTest.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.wrapper;
+
+import javax.jcr.Node;
+
+import org.apache.sling.scripting.RepositoryScriptingTestBase;
+import org.apache.sling.scripting.ScriptEngineHelper;
+
+/** Test access to Version and VersionHistory objects */
+public class ScriptableVersionTest extends RepositoryScriptingTestBase {
+
+ private Node node;
+ private ScriptEngineHelper.Data data = new ScriptEngineHelper.Data();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ node = getNewNode();
+ data.put("node", node);
+ node.addMixin("mix:versionable");
+ getSession().save();
+
+ node.setProperty("Modified", "Just making sure we have a second version");
+ getSession().save();
+ }
+
+ public void testVersionHistoryAccess() throws Exception {
+ Object result = script.eval("node.getVersionHistory().getAllVersions()", data);
+ assertNotNull(result);
+ }
+
+ public void testVersionHistoryIsWrapped() throws Exception {
+ assertEquals("nt:versionHistory", script.eval("node.versionHistory['jcr:primaryType']", data));
+ assertEquals("nt:version", script.eval("node.versionHistory.rootVersion['jcr:primaryType']", data));
+ }
+
+ public void testVersionHistoryWrapperClass() throws Exception {
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableVersionHistory",
+ script.eval("node.versionHistory.javascriptWrapperClass.getName()", data)
+ );
+ }
+
+ public void testVersionAccess() throws Exception {
+ Object result = script.eval("node.getBaseVersion().getCreated()", data);
+ assertNotNull(result);
+ }
+
+ public void testVersionIsWrapped() throws Exception {
+ assertEquals("nt:version", script.eval("node.baseVersion['jcr:primaryType']", data));
+ assertNotNull(script.eval("node.baseVersion.created", data));
+ }
+
+ public void testVersionWrapperClass() throws Exception {
+ assertEquals(
+ "org.apache.sling.scripting.javascript.wrapper.ScriptableVersion",
+ script.eval("node.baseVersion.javascriptWrapperClass.getName()", data)
+ );
+ }
+}
diff --git a/src/test/test-config/jackrabbit-test-config.xml b/src/test/test-config/jackrabbit-test-config.xml
new file mode 100644
index 0000000..c1a408c
--- /dev/null
+++ b/src/test/test-config/jackrabbit-test-config.xml
@@ -0,0 +1,117 @@
+<?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.
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.2//EN"
+ "http://jackrabbit.apache.org/dtd/repository-1.2.dtd">
+<Repository>
+ <!--
+ virtual file system where the repository stores global state
+ (e.g. registered namespaces, custom node types, etc.)
+ -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/repository"/>
+ </FileSystem>
+
+ <!--
+ security configuration
+ -->
+ <Security appName="Jackrabbit">
+ <!--
+ access manager:
+ class: FQN of class implementing the AccessManager interface
+ -->
+ <AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager">
+ <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+ </AccessManager>
+
+ <LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule">
+ <!-- anonymous user name ('anonymous' is the default value) -->
+ <param name="anonymousId" value="anonymous"/>
+ <!--
+ default user name to be used instead of the anonymous user
+ when no login credentials are provided (unset by default)
+ -->
+ <param name="defaultUserId" value="superuser"/>
+ </LoginModule>
+ </Security>
+
+ <!--
+ location of workspaces root directory and name of default workspace
+ -->
+ <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+ <!--
+ workspace configuration template:
+ used to create the initial workspace if there's no workspace yet
+ -->
+ <Workspace name="Jackrabbit Core">
+ <!--
+ virtual file system of the workspace:
+ class: FQN of class implementing the FileSystem interface
+ -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${wsp.home}"/>
+ </FileSystem>
+ <!--
+ persistence manager of the workspace:
+ class: FQN of class implementing the PersistenceManager interface
+ -->
+ <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
+ <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+ <param name="schemaObjectPrefix" value="Jackrabbit Core_"/>
+ </PersistenceManager>
+ <!--
+ Search index and the file system it uses.
+ class: FQN of class implementing the QueryHandler interface
+ -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index"/>
+ </SearchIndex>
+ </Workspace>
+
+ <!--
+ Configures the versioning
+ -->
+ <Versioning rootPath="${rep.home}/version">
+ <!--
+ Configures the filesystem to use for versioning for the respective
+ persistence manager
+ -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/version" />
+ </FileSystem>
+
+ <!--
+ Configures the persistence manager to be used for persisting version state.
+ Please note that the current versioning implementation is based on
+ a 'normal' persistence manager, but this could change in future
+ implementations.
+ -->
+ <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
+ <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+ <param name="schemaObjectPrefix" value="version_"/>
+ </PersistenceManager>
+ </Versioning>
+
+ <!--
+ Search index for content that is shared repository wide
+ (/jcr:system tree, contains mainly versions)
+ -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index"/>
+ </SearchIndex>
+</Repository>
+