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>%&gt;</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>&lt;%=</code> ... <code>%&gt;</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>--&gt;</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>
+