[maven-release-plugin] copy for tag org.apache.sling.commons.testing-2.0.18
git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.commons.testing-2.0.18@1672592 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/testing/README.txt b/testing/README.txt
new file mode 100644
index 0000000..5642f4c
--- /dev/null
+++ b/testing/README.txt
@@ -0,0 +1,26 @@
+Apache Sling Testing Utilities
+
+Tools and utilities for automated testing of Sling modules
+
+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/sling/trunk/commons/testing
+
+See the Subversion documentation for other source control features.
diff --git a/testing/pom.xml b/testing/pom.xml
new file mode 100644
index 0000000..c9e2926
--- /dev/null
+++ b/testing/pom.xml
@@ -0,0 +1,162 @@
+<?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>22</version>
+ <relativePath>../../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.commons.testing</artifactId>
+ <packaging>jar</packaging>
+ <version>2.0.18</version>
+
+ <name>Apache Sling Testing Utilities</name>
+ <description>
+ Tools and utilities for automated testing of Sling modules
+ </description>
+
+ <scm>
+ <connection>
+ scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.commons.testing-2.0.18
+ </connection>
+ <developerConnection>
+ scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.commons.testing-2.0.18
+ </developerConnection>
+ <url>
+ http://svn.apache.org/viewvc/sling/tags/org.apache.sling.commons.testing-2.0.18
+ </url>
+ </scm>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+
+ <!--
+ Dependencies required for testing and provided
+ transitively to modules using this artifact
+ -->
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.4.2</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.jcr.api</artifactId>
+ <version>2.0.6</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <version>2.0</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-api</artifactId>
+ <version>2.2.9</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr-commons</artifactId>
+ <version>2.2.9</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-core</artifactId>
+ <version>2.2.9</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.4</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.adapter</artifactId>
+ <version>2.0.4</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>rhino</groupId>
+ <artifactId>js</artifactId>
+ <version>1.6R6</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ <version>3.1</version>
+ <scope>compile</scope>
+ <exclusions>
+ <!--
+ Same problem as JCR-683: commons-httpclient depends on commons-logging. Since
+ this webapp uses log4j (see the slf4j-log4j12 dependency below), we need to
+ override this dependency with jcl-over-sflf4j, found below
+ -->
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Logging for tests -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <!-- Basic Test Controller JUnit -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
+ <!-- Provide for Test Mocks -->
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpAnyMethod.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpAnyMethod.java
new file mode 100644
index 0000000..7403806
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpAnyMethod.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.commons.testing.integration;
+
+import org.apache.commons.httpclient.HttpMethodBase;
+
+/** Allows any HTTP method for HtttpClient */
+public class HttpAnyMethod extends HttpMethodBase {
+ private final String methodName;
+
+ public HttpAnyMethod(String methodName, String uri) {
+ super(uri);
+ this.methodName = methodName;
+ }
+
+ @Override
+ public String getName() {
+ return methodName;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpStatusCodeException.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpStatusCodeException.java
new file mode 100644
index 0000000..b5f6676
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpStatusCodeException.java
@@ -0,0 +1,52 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+
+@SuppressWarnings("serial")
+public class HttpStatusCodeException extends IOException {
+
+ private final int expectedStatus;
+
+ private final int actualStatus;
+
+ public HttpStatusCodeException(int expectedStatus, int actualStatus,
+ String method, String url) {
+ this(expectedStatus, actualStatus, method, url, null);
+ }
+
+ public HttpStatusCodeException(int expectedStatus, int actualStatus,
+ String method, String url, String content) {
+ super("Expected status code " + expectedStatus + " for " + method
+ + ", got " + actualStatus + ", URL=" + url
+ + (content != null ? ", Content=[" + content + "]" : "")
+ );
+ this.expectedStatus = expectedStatus;
+ this.actualStatus = actualStatus;
+ }
+
+ public int getExpectedStatus() {
+ return expectedStatus;
+ }
+
+ public int getActualStatus() {
+ return actualStatus;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTest.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTest.java
new file mode 100644
index 0000000..bd6544b
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+
+import org.apache.commons.httpclient.HttpClient;
+
+/** Helper class for HTTP tests, extends HttpTestBase and adds
+ * a few utilities that we commonly use in our integration tests.
+ *
+ * Meant to be used as a helper class in JUnit4-style tests, as we
+ * gradually move away from JUnit3 style.
+ */
+public class HttpTest extends HttpTestBase {
+ protected String scriptPath;
+ protected String testText;
+ protected String displayUrl;
+
+ public String uploadTestScript(String localFilename,String filenameOnServer) throws IOException {
+ return uploadTestScript(scriptPath, localFilename, filenameOnServer);
+ }
+
+ public static void assertContains(String content, String expected) {
+ if(!content.contains(expected)) {
+ fail("Content does not contain '" + expected + "' (content=" + content + ")");
+ }
+ }
+
+ public static void assertNotContains(String content, String notExpected) {
+ if(content.contains(notExpected)) {
+ fail("Content contains '" + notExpected + "' (content=" + content + ")");
+ }
+ }
+
+ public void setScriptPath(String scriptPath) {
+ this.scriptPath = scriptPath;
+ }
+
+ public String getScriptPath() {
+ return scriptPath;
+ }
+
+ public SlingIntegrationTestClient getTestClient() {
+ return testClient;
+ }
+
+ public HttpClient getHttpClient() {
+ return httpClient;
+ }
+
+ /** Making this public here, changing the base class to public is not convenient
+ * as many derived classes override it as protected.
+ */
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ /** Making this public here, changing the base class to public is not convenient
+ * as many derived classes override it as protected.
+ */
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
new file mode 100644
index 0000000..d0ab076
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
@@ -0,0 +1,549 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletResponse;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.sling.commons.testing.util.JavascriptEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+/** Base class for HTTP-based Sling Launchpad integration tests */
+public class HttpTestBase extends TestCase {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /** If this system property is set, the startup check is skipped. */
+ public static final String PROPERTY_SKIP_STARTUP_CHECK = "launchpad.skip.startupcheck";
+
+ public static final String HTTP_URL = removeEndingSlash(System.getProperty("launchpad.http.server.url", "http://localhost:8888"));
+ public static final String HTTP_BASE_URL = removePath(HTTP_URL);
+ public static final String WEBDAV_BASE_URL = removeEndingSlash(System.getProperty("launchpad.webdav.server.url", HTTP_BASE_URL));
+ public static final String SERVLET_CONTEXT = removeEndingSlash(System.getProperty("launchpad.servlet.context", getPath(HTTP_URL)));
+
+ public static final String READY_URL_PROP_PREFIX = "launchpad.ready.";
+ public static final int MAX_READY_URL_INDEX = 50;
+
+ /** base path for test files */
+ public static final String TEST_PATH = "/launchpad-integration-tests";
+
+ public static final String HTTP_METHOD_GET = "GET";
+ public static final String HTTP_METHOD_POST = "POST";
+
+ public static final String CONTENT_TYPE_HTML = "text/html";
+ public static final String CONTENT_TYPE_XML = "application/xml";
+ public static final String CONTENT_TYPE_PLAIN = "text/plain";
+ public static final String CONTENT_TYPE_JSON = "application/json";
+ public static final String CONTENT_TYPE_JS = "application/javascript";
+ public static final String CONTENT_TYPE_CSS = "text/css";
+
+ public static final String SLING_RESOURCE_TYPE = "sling:resourceType";
+
+ public static final String SLING_POST_SERVLET_CREATE_SUFFIX = "/";
+ public static final String DEFAULT_EXT = ".txt";
+
+ public static final String EXECUTE_RESOURCE_TYPE = "SlingTesting" + HttpTestBase.class.getSimpleName();
+ private static int executeCounter;
+
+ public static final int READY_TIMEOUT_SECONDS = Integer.getInteger("HttpTestBase.readyTimeoutSeconds", 60);
+
+ protected SlingIntegrationTestClient testClient;
+ protected HttpClient httpClient;
+
+ private static Boolean slingStartupOk;
+
+ /** Means "don't care about Content-Type" in getContent(...) methods */
+ public static final String CONTENT_TYPE_DONTCARE = "*";
+
+ private static final Object startupCheckLock = new Object();
+
+ /** URLs stored here are deleted in tearDown */
+ protected final List<String> urlsToDelete = new LinkedList<String>();
+
+ /** Need to execute javascript code */
+ private final JavascriptEngine javascriptEngine = new JavascriptEngine();
+
+ /** Class that creates a test node under the given parentPath, and
+ * stores useful values for testing. Created for JspScriptingTest,
+ * older test classes do not use it, but it might simplify them.
+ */
+ protected class TestNode extends HttpTestNode {
+ public TestNode(String parentPath, Map<String, String> properties) throws IOException {
+ super(testClient, parentPath, properties);
+ }
+ }
+
+ public static String removeEndingSlash(String str) {
+ if(str != null && str.endsWith("/")) {
+ return str.substring(0, str.length() - 1);
+ }
+ return str;
+ }
+
+ private static String removePath(String str) {
+ final int pos = str.indexOf(":/");
+ final int slashPos = str.indexOf('/', pos+3);
+ if ( slashPos != -1 ) {
+ return str.substring(0, slashPos);
+ }
+ return str;
+ }
+
+ private static String getPath(String str) {
+ final int pos = str.indexOf(":/");
+ final int slashPos = str.indexOf('/', pos+3);
+ if ( slashPos != -1 ) {
+ return str.substring(slashPos);
+ }
+ return "";
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ MDC.put("testclass", getClass().getName());
+ MDC.put("testcase", getName());
+
+ // assume http and webdav are on the same host + port
+ URL url = null;
+ try {
+ url = new URL(HTTP_BASE_URL);
+ } catch(MalformedURLException mfe) {
+ // MalformedURLException doesn't tell us the URL by default
+ throw new IOException("MalformedURLException: " + HTTP_BASE_URL);
+ }
+
+ // setup HTTP client, with authentication (using default Jackrabbit credentials)
+ httpClient = new TestInfoPassingClient();
+ httpClient.getParams().setAuthenticationPreemptive(true);
+ Credentials defaultcreds = getDefaultCredentials();
+ httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), AuthScope.ANY_REALM), defaultcreds);
+
+ testClient = new SlingIntegrationTestClient(httpClient);
+
+ waitForSlingStartup();
+ }
+
+ /**
+ * Generate default credentials used for HTTP requests.
+ */
+ public Credentials getDefaultCredentials() {
+ return new UsernamePasswordCredentials("admin", "admin");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ MDC.remove("testcase");
+ MDC.remove("testclass");
+
+ super.tearDown();
+
+ for(String url : urlsToDelete) {
+ testClient.delete(url);
+ }
+ }
+
+ /** On the server side, initialization of Sling bundles is done
+ * asynchronously once the webapp is started. This method checks
+ * that everything's ready on the server side, by calling an URL
+ * that requires the SlingPostServlet and the JCR repository to
+ * work correctly.
+ */
+ protected void waitForSlingStartup() throws Exception {
+ // Use a static flag to make sure this runs only once in our test suite
+ // we must synchronize on this if we don't 2 threads could enter the check concurrently
+ // which would leave to random results.
+ synchronized(startupCheckLock) {
+ if (slingStartupOk != null) {
+ if(slingStartupOk) {
+ return;
+ }
+ fail("Sling services not available. Already checked in earlier tests.");
+ }
+ if ( System.getProperty(PROPERTY_SKIP_STARTUP_CHECK) != null ) {
+ slingStartupOk = true;
+ return;
+ }
+ slingStartupOk = false;
+ }
+
+ System.err.println("Checking if the required Sling services are started (timeout " + READY_TIMEOUT_SECONDS + " seconds)...");
+ System.err.println("(base URLs=" + HTTP_BASE_URL + " and " + WEBDAV_BASE_URL + "; servlet context="+ SERVLET_CONTEXT +")");
+
+ // Try creating a node on server, every 500msec, until ok, with timeout
+ final List<String> exceptionMessages = new LinkedList<String>();
+ final long maxMsecToWait = READY_TIMEOUT_SECONDS * 1000L;
+ final long startupTime = System.currentTimeMillis();
+ String lastException = "";
+ int nTimesOk = 0;
+
+ // Wait until slingServerReady returns true this many times,
+ // as in some cases more initializations might take place after
+ // this returns true
+ final int MIN_TIMES_OK = 4;
+
+ while(!slingStartupOk && (System.currentTimeMillis() < startupTime + maxMsecToWait) ) {
+ try {
+ if(slingServerReady()) {
+ nTimesOk++;
+ if(nTimesOk >= MIN_TIMES_OK) {
+ slingStartupOk = true;
+ break;
+ }
+ } else {
+ nTimesOk = 0;
+ }
+ } catch(Exception e) {
+ nTimesOk = 0;
+ final String newX = e.toString();
+ if(!lastException.equals(newX)) {
+ exceptionMessages.add(newX);
+ }
+ lastException = newX;
+ }
+ Thread.sleep(500L);
+ }
+
+ if(slingStartupOk) {
+ log.info("Sling server found ready after {} msec", System.currentTimeMillis() - startupTime);
+ } else {
+ StringBuffer msg = new StringBuffer("Server does not seem to be ready, after ");
+ msg.append(maxMsecToWait).append(" msec, got the following ").append(exceptionMessages.size()).append(" Exceptions:");
+ for (String e: exceptionMessages) {
+ msg.append(e).append("\n");
+ }
+ System.err.println(msg);
+ fail(msg.toString());
+ }
+
+ System.err.println("Sling services seem to be started, continuing with integration tests.");
+ }
+
+ /** Return true if able to create and retrieve a node on server */
+ protected boolean slingServerReady() throws Exception {
+ // create a node on the server
+ final String time = String.valueOf(System.currentTimeMillis());
+ final String url = HTTP_BASE_URL + "/WaitForSlingStartup/" + time;
+
+ // add some properties to the node
+ final Map<String,String> props = new HashMap<String,String>();
+ props.put("time", time);
+
+ // POST, get URL of created node and get content
+ String urlOfNewNode = null;
+ try {
+ urlOfNewNode = testClient.createNode(url, props, null, true);
+ final GetMethod get = new GetMethod(urlOfNewNode + DEFAULT_EXT);
+ final int status = httpClient.executeMethod(get);
+ if(status!=200) {
+ throw new HttpStatusCodeException(200, status, "GET", urlOfNewNode);
+ }
+
+ final Header h = get.getResponseHeader("Content-Type");
+ final String contentType = h==null ? "" : h.getValue();
+ if(!contentType.startsWith("text/plain")) {
+ throw new IOException("Expected Content-Type=text/plain but got '" + contentType + "' for URL=" + urlOfNewNode);
+ }
+
+ final String content = get.getResponseBodyAsString();
+ if(!content.contains(time)) {
+ throw new IOException("Content does not contain '" + time + "' (" + content + ") at URL=" + urlOfNewNode);
+ }
+ } finally {
+ if(urlOfNewNode != null) {
+ try {
+ testClient.delete(urlOfNewNode);
+ } catch(Exception ignore) {
+ }
+ }
+ }
+
+ // Also check that the WebDAV root is ready
+ {
+ // need the trailing slash in case the base URL is the context root
+ final String webDavUrl = WEBDAV_BASE_URL + "/";
+ final HttpAnyMethod options = new HttpAnyMethod("OPTIONS",webDavUrl);
+ final int status = httpClient.executeMethod(options);
+ if(status!=200) {
+ throw new HttpStatusCodeException(200, status, "OPTIONS", webDavUrl);
+ }
+
+ // The Allow header tells us that we're talking to a WebDAV server
+ final Header h = options.getResponseHeader("Allow");
+ if(h == null) {
+ throw new IOException("Response does not contain Allow header, at URL=" + webDavUrl);
+ } else if(h.getValue() == null) {
+ throw new IOException("Allow header has null value at URL=" + webDavUrl);
+ } else if(!h.getValue().contains("PROPFIND")) {
+ throw new IOException("Allow header (" + h.getValue() + " does not contain PROPFIND, at URL=" + webDavUrl);
+ }
+ }
+
+ // And check optional additional URLs for readyness
+ // Defined by system properties like
+ // launchpad.ready.1 = GET:/tmp/someUrl:200:expectedRegexpInResponse
+ {
+ for(int i=0; i <= MAX_READY_URL_INDEX ; i++) {
+ final String propName = READY_URL_PROP_PREFIX + i;
+ final String readyDef = System.getProperty(propName, "");
+ final String [] parts = readyDef.split(":");
+ if(parts.length == 4) {
+ final String info = propName + "=" + readyDef;
+ final HttpAnyMethod m = new HttpAnyMethod(parts[0],HTTP_BASE_URL + parts[1]);
+ final int expectedStatus = Integer.valueOf(parts[2]);
+ final int status = httpClient.executeMethod(m);
+ if(expectedStatus != status) {
+ throw new IOException("Status " + status + " does not match expected value: " + info);
+ }
+ final String content = m.getResponseBodyAsString();
+ final Pattern p = Pattern.compile("(?s).*" + parts[3] + ".*");
+ if(!p.matcher(content).matches()) {
+ throw new IOException("Content does not match expected regexp:" + info + ", content=" + content);
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /** Verify that given URL returns expectedStatusCode
+ * @return the HttpMethod executed
+ * @throws IOException */
+ public HttpMethod assertHttpStatus(String urlString, int expectedStatusCode, String assertMessage) throws IOException {
+ final GetMethod get = new GetMethod(urlString);
+ final int status = httpClient.executeMethod(get);
+ if(assertMessage == null) {
+ assertEquals(urlString,expectedStatusCode, status);
+ } else {
+ assertEquals(assertMessage, expectedStatusCode, status);
+ }
+ return get;
+ }
+
+ /** Verify that given URL returns expectedStatusCode
+ * @return the HttpMethod executed
+ * @throws IOException */
+ public HttpMethod assertHttpStatus(String urlString, int expectedStatusCode) throws IOException {
+ return assertHttpStatus(urlString, expectedStatusCode, null);
+ }
+
+ /** Execute a POST request and check status
+ * @return the HttpMethod executed
+ * @throws IOException */
+ public HttpMethod assertPostStatus(String url, int expectedStatusCode, List<NameValuePair> postParams, String assertMessage)
+ throws IOException {
+ final PostMethod post = new PostMethod(url);
+ post.setFollowRedirects(false);
+
+ if(postParams!=null) {
+ final NameValuePair [] nvp = {};
+ post.setRequestBody(postParams.toArray(nvp));
+ }
+
+ final int status = httpClient.executeMethod(post);
+ if(assertMessage == null) {
+ assertEquals(expectedStatusCode, status);
+ } else {
+ assertEquals(assertMessage, expectedStatusCode, status);
+ }
+ return post;
+ }
+
+ /** retrieve the contents of given URL and assert its content type (default to HTTP GET method)*/
+ public String getContent(String url, String expectedContentType) throws IOException {
+ return getContent(url, expectedContentType, null);
+ }
+
+ /** retrieve the contents of given URL and assert its content type (default to HTTP GET method)*/
+ public String getContent(String url, String expectedContentType, List<NameValuePair> params) throws IOException {
+ return getContent(url, expectedContentType, params, HttpServletResponse.SC_OK);
+ }
+
+ /** retrieve the contents of given URL and assert its content type (default to HTTP GET method)
+ * @param expectedContentType use CONTENT_TYPE_DONTCARE if must not be checked
+ * @throws IOException
+ * @throws HttpException */
+ public String getContent(String url, String expectedContentType, List<NameValuePair> params, int expectedStatusCode) throws IOException {
+ return getContent(url, expectedContentType, params, expectedStatusCode, HTTP_METHOD_GET);
+ }
+
+ /** retrieve the contents of given URL and assert its content type
+ * @param expectedContentType use CONTENT_TYPE_DONTCARE if must not be checked
+ * @param httMethod supports just GET and POST methods
+ * @throws IOException
+ * @throws HttpException */
+ public String getContent(String url, String expectedContentType, List<NameValuePair> params, int expectedStatusCode, String httpMethod) throws IOException {
+ HttpMethodBase method = null;
+
+ if (HTTP_METHOD_GET.equals(httpMethod)){
+ method= new GetMethod(url);
+ }else if (HTTP_METHOD_POST.equals(httpMethod)){
+ method = new PostMethod(url);
+ } else{
+ fail("Http Method not supported in this test suite, method: "+httpMethod);
+ }
+
+ if(params != null) {
+ final NameValuePair [] nvp = new NameValuePair[0];
+ method.setQueryString(params.toArray(nvp));
+ }
+ final int status = httpClient.executeMethod(method);
+ final String content = getResponseBodyAsStream(method, 0);
+ assertEquals("Expected status " + expectedStatusCode + " for " + url + " (content=" + content + ")",
+ expectedStatusCode,status);
+ final Header h = method.getResponseHeader("Content-Type");
+ if(expectedContentType == null) {
+ if(h!=null) {
+ fail("Expected null Content-Type, got " + h.getValue());
+ }
+ } else if(CONTENT_TYPE_DONTCARE.equals(expectedContentType)) {
+ // no check
+ } else if(h==null) {
+ fail(
+ "Expected Content-Type that starts with '" + expectedContentType
+ +" but got no Content-Type header at " + url
+ );
+ } else {
+ assertTrue(
+ "Expected Content-Type that starts with '" + expectedContentType
+ + "' for " + url + ", got '" + h.getValue() + "'",
+ h.getValue().startsWith(expectedContentType)
+ );
+ }
+ return content.toString();
+
+ }
+
+
+ /** upload rendering test script, and return its URL for future deletion */
+ public String uploadTestScript(String scriptPath, String localFilename,String filenameOnServer) throws IOException {
+ final String url = WEBDAV_BASE_URL + scriptPath + "/" + filenameOnServer;
+ final String testFile = "/integration-test/" + localFilename;
+ final InputStream data = getClass().getResourceAsStream(testFile);
+ if(data==null) {
+ fail("Test file not found:" + testFile);
+ }
+ try {
+ testClient.upload(url, data);
+ } finally {
+ if(data!=null) {
+ data.close();
+ }
+ }
+ return url;
+ }
+
+ /** Upload script, execute with no parameters and return content */
+ public String executeScript(String localFilename) throws Exception {
+ return executeScript(localFilename, null);
+ }
+
+ /** Upload script, execute with given parameters (optional) and return content */
+ public String executeScript(String localFilename, List<NameValuePair> params) throws Exception {
+
+ // Use unique resource type
+ int counter = 0;
+ synchronized (getClass()) {
+ counter = ++executeCounter;
+ }
+ final String resourceType = EXECUTE_RESOURCE_TYPE + counter;
+ final String scriptPath = "/apps/" + resourceType;
+ testClient.mkdirs(WEBDAV_BASE_URL , scriptPath);
+
+ final int pos = localFilename.lastIndexOf(".");
+ if(pos < 1) {
+ throw new IllegalArgumentException("localFilename must have extension (" + localFilename + ")");
+ }
+ final String ext = localFilename.substring(pos + 1);
+ final List<String> toDelete = new LinkedList<String>();
+ try {
+ toDelete.add(uploadTestScript(scriptPath, localFilename, "txt." + ext));
+ final Map<String, String> props = new HashMap<String, String>();
+ props.put(SLING_RESOURCE_TYPE, resourceType);
+ final String nodePath = scriptPath + "/node" + counter;
+ final String nodeUrl = testClient.createNode(HTTP_BASE_URL + nodePath, props);
+ toDelete.add(nodeUrl);
+ return getContent(nodeUrl + ".txt", CONTENT_TYPE_DONTCARE, params);
+ } finally {
+ for(String url : toDelete) {
+ testClient.delete(url);
+ }
+ }
+ }
+
+ public void assertJavascript(String expectedOutput, String jsonData, String code) throws IOException {
+ assertJavascript(expectedOutput, jsonData, code, null);
+ }
+
+ /** Evaluate given code using given jsonData as the "data" object */
+ public void assertJavascript(String expectedOutput, String jsonData, String code, String testInfo) throws IOException {
+ final String result = javascriptEngine.execute(code, jsonData);
+ if(!result.equals(expectedOutput)) {
+ fail(
+ "Expected '" + expectedOutput
+ + "' but got '" + result
+ + "' for script='" + code + "'"
+ + "' and data='" + jsonData + "'"
+ + (testInfo==null ? "" : ", test info=" + testInfo)
+ );
+ }
+ }
+
+ /** Return m's response body as a string, optionally limiting the length that we read
+ * @param maxLength if 0, no limit
+ */
+ public static String getResponseBodyAsStream(HttpMethodBase m, int maxLength) throws IOException {
+ final InputStream is = m.getResponseBodyAsStream();
+ final StringBuilder content = new StringBuilder();
+ final String charset = m.getResponseCharSet();
+ final byte [] buffer = new byte[16384];
+ int n = 0;
+ while( (n = is.read(buffer, 0, buffer.length)) > 0) {
+ content.append(new String(buffer, 0, n, charset));
+ if(maxLength != 0 && content.length() > maxLength) {
+ throw new IllegalArgumentException("Response body size is over maxLength (" + maxLength + ")");
+ }
+ }
+ return content.toString();
+ }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestNode.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestNode.java
new file mode 100644
index 0000000..77af85f
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestNode.java
@@ -0,0 +1,48 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Utility to manage test nodes */
+public class HttpTestNode {
+ public final String testText;
+ public final String nodeUrl;
+ public final String resourceType;
+ public final String scriptPath;
+ private final SlingIntegrationTestClient testClient;
+
+ public HttpTestNode(SlingIntegrationTestClient testClient, String parentPath, Map<String, String> properties) throws IOException {
+ this.testClient = testClient;
+ if(properties == null) {
+ properties = new HashMap<String, String>();
+ }
+ testText = "This is a test node " + System.currentTimeMillis();
+ properties.put("text", testText);
+ nodeUrl = testClient.createNode(parentPath + HttpTestBase.SLING_POST_SERVLET_CREATE_SUFFIX, properties);
+ resourceType = properties.get(HttpTestBase.SLING_RESOURCE_TYPE);
+ scriptPath = "/apps/" + (resourceType == null ? "nt/unstructured" : resourceType);
+ testClient.mkdirs(HttpTestBase.WEBDAV_BASE_URL, scriptPath);
+ }
+
+ public void delete() throws IOException {
+ testClient.delete(nodeUrl);
+ }
+
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePair.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePair.java
new file mode 100644
index 0000000..fe002cb
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePair.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.commons.testing.integration;
+
+
+/**
+ * A basic name-value pair class.
+ */
+public class NameValuePair {
+
+ public NameValuePair() {
+ this(null, null);
+ }
+
+ public NameValuePair(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ private String name = null;
+
+ private String value = null;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("name=%s, value=%s", name, value);
+ }
+
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePairList.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePairList.java
new file mode 100644
index 0000000..39cfff2
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/NameValuePairList.java
@@ -0,0 +1,125 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+/**
+ * A list of name-value pairs.
+ */
+public class NameValuePairList implements Iterable<NameValuePair> {
+
+ private final List<NameValuePair> delegate;
+
+ public NameValuePairList() {
+ delegate = new ArrayList<NameValuePair>();
+ }
+
+ public NameValuePairList(List<NameValuePair> init) {
+ delegate = new ArrayList<NameValuePair>(init);
+ }
+
+ public NameValuePairList(NameValuePairList clientNodeProperties) {
+ this(clientNodeProperties.delegate);
+ }
+
+ public NameValuePairList(Map<String, String> clientNodeProperties) {
+ this();
+ if (clientNodeProperties != null) {
+ for (Map.Entry<String, String> e : clientNodeProperties.entrySet()) {
+ add(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ public void add(String name, String value) {
+ delegate.add(new NameValuePair(name, value));
+ }
+
+ public void addIfNew(String name, String value) {
+ boolean found = false;
+ for (ListIterator<NameValuePair> li = delegate.listIterator(); li.hasNext();) {
+ NameValuePair current = li.next();
+ if (current.getName().equals(name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ delegate.add(new NameValuePair(name, value));
+ }
+
+ }
+
+ public void addOrReplace(String name, String value) {
+ boolean replaced = false;
+ for (ListIterator<NameValuePair> li = delegate.listIterator(); li.hasNext();) {
+ NameValuePair current = li.next();
+ if (current.getName().equals(name)) {
+ if (!replaced) {
+ current.setValue(value);
+ replaced = true;
+ } else {
+ li.remove();
+ }
+ }
+ }
+
+ if (!replaced) {
+ delegate.add(new NameValuePair(name, value));
+ }
+ }
+
+ public void addOrReplaceAll(NameValuePairList other) {
+ for (NameValuePair nvp : other) {
+ addOrReplace(nvp.getName(), nvp.getValue());
+ }
+ }
+
+ public void clear() {
+ delegate.clear();
+ }
+
+ public Iterator<NameValuePair> iterator() {
+ return delegate.iterator();
+ }
+
+ public void prependIfNew(String name, String value) {
+ boolean found = false;
+ for (ListIterator<NameValuePair> li = delegate.listIterator(); li.hasNext();) {
+ NameValuePair current = li.next();
+ if (current.getName().equals(name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ delegate.add(0, new NameValuePair(name, value));
+ }
+
+ }
+
+ public int size() {
+ return delegate.size();
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/SlingIntegrationTestClient.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/SlingIntegrationTestClient.java
new file mode 100644
index 0000000..9263598
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/SlingIntegrationTestClient.java
@@ -0,0 +1,267 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
+import org.apache.commons.httpclient.methods.multipart.StringPart;
+
+/** Client functions to interact with Sling in integration tests */
+public class SlingIntegrationTestClient {
+ private final HttpClient httpClient;
+
+ public SlingIntegrationTestClient(HttpClient client) {
+ this.httpClient = client;
+ }
+
+ /** Upload a file to the Sling repository
+ * @return the HTTP status code
+ */
+ public int upload(String toUrl, InputStream is) throws IOException {
+ final PutMethod put = new PutMethod(toUrl);
+ put.setRequestEntity(new InputStreamRequestEntity(is));
+ return httpClient.executeMethod(put);
+ }
+
+ /** Delete a file from the Sling repository
+ * @return the HTTP status code
+ */
+ public int delete(String url) throws IOException {
+ final DeleteMethod delete = new DeleteMethod(url);
+ return httpClient.executeMethod(delete);
+ }
+
+ /** Create the given directory via WebDAV, if needed, under given URL */
+ public void mkdir(String url) throws IOException {
+ int status = 0;
+ status = httpClient.executeMethod(new GetMethod(url + ".txt"));
+ if(status != 200) {
+ status = httpClient.executeMethod(new HttpAnyMethod("MKCOL",url));
+ if(status!=201) {
+ throw new IOException("mkdir(" + url + ") failed, status code=" + status);
+ }
+ }
+ }
+
+ /** Create the given directory via WebDAV, including parent directories */
+ public void mkdirs(String baseUrl,String path) throws IOException {
+ final String [] paths = path.split("/");
+ if(baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0,baseUrl.length() - 1);
+ }
+
+ String currentPath = baseUrl;
+ for(String pathElement : paths) {
+ if(pathElement.length() == 0) {
+ continue;
+ }
+ currentPath += "/" + pathElement;
+ mkdir(currentPath);
+ }
+
+ final String url = baseUrl + path;
+ final int status = httpClient.executeMethod(new GetMethod(url + ".txt"));
+ if(status!=200) {
+ throw new HttpStatusCodeException(200, status, "GET", url);
+ }
+ }
+
+ /** Call the other createNode method with headers==null */
+ public String createNode(String url, Map<String,String> nodeProperties) throws IOException {
+ return createNode(url, nodeProperties, null, false);
+ }
+
+ /** Create a node under given path, using a POST to Sling
+ * @param url under which node is created
+ * @param multiPart if true, does a multipart POST
+ * @return the URL that Sling provides to display the node
+ */
+ public String createNode(String url, Map<String,String> clientNodeProperties, Map<String,String> requestHeaders,boolean multiPart)
+ throws IOException {
+ return createNode(url, new NameValuePairList(clientNodeProperties), requestHeaders, multiPart);
+ }
+
+ /** Create a node under given path, using a POST to Sling
+ * @param url under which node is created
+ * @param multiPart if true, does a multipart POST
+ * @return the URL that Sling provides to display the node
+ */
+ public String createNode(String url, NameValuePairList clientNodeProperties, Map<String,String> requestHeaders, boolean multiPart)
+ throws IOException {
+ return createNode(url, clientNodeProperties, requestHeaders, multiPart, null, null, null);
+ }
+
+ /** Create a node under given path, using a POST to Sling
+ * @param url under which node is created
+ * @param multiPart if true, does a multipart POST
+ * @param localFile file to upload
+ * @param fieldName name of the file field
+ * @param typeHint typeHint of the file field
+ * @return the URL that Sling provides to display the node
+ */
+ public String createNode(String url, NameValuePairList clientNodeProperties, Map<String,String> requestHeaders, boolean multiPart,
+ File localFile, String fieldName, String typeHint)
+ throws IOException {
+
+ final PostMethod post = new PostMethod(url);
+ post.setFollowRedirects(false);
+
+ // create a private copy of the properties to not tamper with
+ // the properties of the client
+ NameValuePairList nodeProperties = new NameValuePairList(clientNodeProperties);
+
+ // add sling specific properties
+ nodeProperties.prependIfNew(":redirect", "*");
+ nodeProperties.prependIfNew(":displayExtension", "");
+ nodeProperties.prependIfNew(":status", "browser");
+
+ // add fake property - otherwise the node is not created
+ if (clientNodeProperties == null) {
+ nodeProperties.add("jcr:created", "");
+ }
+
+ // force form encoding to UTF-8, which is what we use to convert the
+ // string parts into stream data
+ nodeProperties.addOrReplace("_charset_", "UTF-8");
+
+ if( nodeProperties.size() > 0) {
+ if(multiPart) {
+ final List<Part> partList = new ArrayList<Part>();
+ for(NameValuePair e : nodeProperties) {
+ if (e.getValue() != null) {
+ partList.add(new StringPart(e.getName(), e.getValue(), "UTF-8"));
+ }
+ }
+ if (localFile != null) {
+ partList.add(new FilePart(fieldName, localFile));
+ if (typeHint != null) {
+ partList.add(new StringPart(fieldName + "@TypeHint", typeHint));
+ }
+ }
+ final Part [] parts = partList.toArray(new Part[partList.size()]);
+ post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
+ } else {
+ post.getParams().setContentCharset("UTF-8");
+ for(NameValuePair e : nodeProperties) {
+ post.addParameter(e.getName(),e.getValue());
+ }
+ }
+ }
+
+ if(requestHeaders != null) {
+ for(Map.Entry<String,String> e : requestHeaders.entrySet()) {
+ post.addRequestHeader(e.getKey(), e.getValue());
+ }
+ }
+
+ final int expected = 302;
+ final int status = httpClient.executeMethod(post);
+ if(status!=expected) {
+ throw new HttpStatusCodeException(expected, status, "POST", url, HttpTestBase.getResponseBodyAsStream(post, 0));
+ }
+ String location = post.getResponseHeader("Location").getValue();
+ post.releaseConnection();
+ // simple check if host is missing
+ if (!location.startsWith("http://")) {
+ String host = HttpTestBase.HTTP_BASE_URL;
+ int idx = host.indexOf('/', 8);
+ if (idx > 0) {
+ host = host.substring(0, idx);
+ }
+ location = host + location;
+ }
+ return location;
+ }
+
+ /** Upload to an file node structure, see SLING-168 */
+ public void uploadToFileNode(String url, File localFile, String fieldName, String typeHint)
+ throws IOException {
+
+ final Part[] parts = new Part[typeHint == null ? 1 : 2];
+ parts[0] = new FilePart(fieldName, localFile);
+ if (typeHint != null) {
+ parts[1] = new StringPart(fieldName + "@TypeHint", typeHint);
+ }
+ final PostMethod post = new PostMethod(url);
+ post.setFollowRedirects(false);
+ post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
+
+ final int status = httpClient.executeMethod(post);
+ final int expected = 200;
+ if(status!=expected) {
+ throw new HttpStatusCodeException(expected, status, "POST", HttpTestBase.getResponseBodyAsStream(post, 0));
+ }
+ }
+
+ /** Upload multiple files to file node structures */
+ public void uploadToFileNodes(String url, File[] localFiles, String[] fieldNames, String[] typeHints)
+ throws IOException {
+
+ List<Part> partsList = new ArrayList<Part>();
+ for (int i=0; i < localFiles.length; i++) {
+ Part filePart = new FilePart(fieldNames[i], localFiles[i]);
+ partsList.add(filePart);
+ if (typeHints != null) {
+ Part typeHintPart = new StringPart(fieldNames[i] + "@TypeHint", typeHints[i]);
+ partsList.add(typeHintPart);
+ }
+ }
+
+ final Part[] parts = partsList.toArray(new Part[partsList.size()]);
+ final PostMethod post = new PostMethod(url);
+ post.setFollowRedirects(false);
+ post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
+
+ final int expected = 200;
+ final int status = httpClient.executeMethod(post);
+ if(status!=expected) {
+ throw new HttpStatusCodeException(expected, status, "POST", HttpTestBase.getResponseBodyAsStream(post, 0));
+ }
+ }
+
+ public int post(String url, Map<String,String> properties) throws HttpException, IOException {
+ final PostMethod post = new PostMethod(url);
+ post.getParams().setContentCharset("UTF-8");
+ for(Entry<String, String> e : properties.entrySet()) {
+ post.addParameter(e.getKey(), e.getValue());
+ }
+ return httpClient.executeMethod(post);
+ }
+
+ public int get(String url) throws HttpException, IOException {
+ final GetMethod get = new GetMethod(url);
+ get.getParams().setContentCharset("UTF-8");
+ return httpClient.executeMethod(get);
+ }
+
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java b/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
new file mode 100644
index 0000000..314e33b
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.httpclient.HostConfiguration;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpState;
+import org.slf4j.MDC;
+
+/**
+ * HttpClient extension which also passes test related headers as part
+ * of outgoing HTTP request
+ */
+public class TestInfoPassingClient extends HttpClient {
+ //Defined in org.apache.sling.testing.tools.junit.TestLogRule
+ private static final String SLING_HEADER_PREFIX = "X-Sling-";
+
+ @Override
+ public int executeMethod(HostConfiguration hostconfig, HttpMethod method,
+ HttpState state) throws IOException {
+ addSlingHeaders(method);
+ return super.executeMethod(hostconfig, method, state);
+ }
+
+ /**
+ * Adds all MDC key-value pairs as HTTP header where the key starts
+ * with 'X-Sling-'
+ */
+ private static void addSlingHeaders(HttpMethod m){
+ Map<?,?> mdc = MDC.getCopyOfContextMap();
+ if (mdc != null) {
+ for (Map.Entry<?, ?> e : mdc.entrySet()) {
+ Object key = e.getKey();
+ if (key instanceof String
+ && ((String)key).startsWith(SLING_HEADER_PREFIX)
+ && e.getValue() instanceof String) {
+ m.addRequestHeader((String) key, (String) e.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/EventHelper.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/EventHelper.java
new file mode 100644
index 0000000..f11fc53
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/EventHelper.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.commons.testing.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+/** Used by tests to wait until JCR notification events
+ * have been delivered.
+ */
+public class EventHelper implements EventListener {
+ private final Session session;
+ private int eventCount;
+ public static final String WAIT_NODE_FOLDER = "WAIT_NODE";
+ public static final String WAIT_NODE_NODE = EventHelper.class.getSimpleName();
+ private final Node waitNodeFolder;
+
+ public EventHelper(Session s) throws RepositoryException {
+ session = s;
+
+ final int eventTypes = Event.NODE_ADDED | Event.NODE_REMOVED;
+ final boolean isDeep = true;
+ final boolean noLocal = false;
+ session.getWorkspace().getObservationManager().addEventListener(
+ this, eventTypes, "/" + WAIT_NODE_FOLDER, isDeep, null, null, noLocal);
+
+ if(session.getRootNode().hasNode(WAIT_NODE_FOLDER)) {
+ waitNodeFolder = session.getRootNode().getNode(WAIT_NODE_FOLDER);
+ } else {
+ waitNodeFolder = session.getRootNode().addNode(WAIT_NODE_FOLDER, "nt:unstructured");
+ }
+ session.save();
+ }
+
+ public void onEvent(EventIterator it) {
+ eventCount++;
+ }
+
+ /** To make sure observation events have been delivered,
+ * create or delete a a node and wait for the corresponding
+ * events to be received.
+ */
+ public void waitForEvents(long timeoutMsec) throws RepositoryException {
+ final int targetEventCount = eventCount + 1;
+
+ if(waitNodeFolder.hasNode(WAIT_NODE_NODE)) {
+ waitNodeFolder.getNode(WAIT_NODE_NODE).remove();
+ } else {
+ waitNodeFolder.addNode(WAIT_NODE_NODE);
+ }
+ session.save();
+
+ final long end = System.currentTimeMillis() + timeoutMsec;
+ while(eventCount < targetEventCount && System.currentTimeMillis() < end) {
+ try {
+ Thread.sleep(100);
+ } catch(InterruptedException ignored) {
+ }
+ }
+
+ if(eventCount < targetEventCount) {
+ throw new IllegalStateException("Event counter did not reach " + targetEventCount + ", waited " + timeoutMsec + " msec");
+ }
+ }
+
+ /**
+ * Remove the event listener from the observation listener.
+ */
+ public void dispose() {
+ try {
+ session.getWorkspace().getObservationManager().removeEventListener(this);
+ } catch (RepositoryException e) {
+ }
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNode.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNode.java
new file mode 100644
index 0000000..997a41e
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNode.java
@@ -0,0 +1,489 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Binary;
+import javax.jcr.InvalidLifecycleTransitionException;
+import javax.jcr.Item;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+import javax.jcr.version.VersionHistory;
+
+import org.apache.jackrabbit.util.ChildrenCollectorFilter;
+
+// simple mock implementation of a node
+public class MockNode implements Node {
+
+ private String path;
+ private Map <String, Property> properties = new HashMap <String, Property>();
+
+ private NodeType nodeType;
+ private Session session;
+
+ public MockNode(String path) {
+ this(path, null);
+ }
+
+ public MockNode(String path, String type) {
+ this.path = path;
+ this.nodeType = new MockNodeType(type);
+ }
+
+ public String getName() {
+ return path.substring(path.lastIndexOf('/') + 1);
+ }
+
+ public Node getParent() {
+ return new MockNode(path.substring(0, path.lastIndexOf('/')));
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public NodeType getPrimaryNodeType() {
+ return nodeType;
+ }
+
+ public boolean isSame(Item otherItem) {
+ return equals(otherItem);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof MockNode)) {
+ return false;
+ }
+
+ return ((MockNode) obj).getPath().equals(getPath());
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "MockNode: path=" + getPath();
+ }
+
+ public void addMixin(String mixinName) {
+ }
+
+ public Node addNode(String relPath) {
+ return null;
+ }
+
+ public Node addNode(String relPath, String primaryNodeTypeName) {
+ return null;
+ }
+
+ public boolean canAddMixin(String mixinName) {
+ return false;
+ }
+
+ public void cancelMerge(Version version) {
+
+ }
+
+ public Version checkin() {
+ return null;
+ }
+
+ public void checkout() {
+ }
+
+ public void doneMerge(Version version) {
+ }
+
+ public Version getBaseVersion() {
+ return null;
+ }
+
+ public String getCorrespondingNodePath(String workspaceName) {
+ return null;
+ }
+
+ public NodeDefinition getDefinition() {
+ return null;
+ }
+
+ public int getIndex() {
+ return 0;
+ }
+
+ public Lock getLock() {
+ return null;
+ }
+
+ public NodeType[] getMixinNodeTypes() {
+ return null;
+ }
+
+ public Node getNode(String relPath) {
+ return new MockNode(path + "/" + relPath);
+ }
+
+ public NodeIterator getNodes() {
+ return new MockNodeIterator();
+ }
+
+ public NodeIterator getNodes(String namePattern) {
+ return new MockNodeIterator();
+ }
+
+ public Item getPrimaryItem() {
+ return null;
+ }
+
+ public PropertyIterator getProperties() {
+ return new MockPropertyIterator(properties.values().iterator());
+ }
+
+ public PropertyIterator getProperties(String namePattern) throws RepositoryException {
+ PropertyIterator iterator = getProperties();
+ List<Property> properties = new ArrayList<Property>();
+
+ while (iterator.hasNext()) {
+ Property p = iterator.nextProperty();
+ String name = p.getName();
+ if (ChildrenCollectorFilter.matches(name, namePattern)) {
+ properties.add(p);
+ }
+ }
+
+ return new MockPropertyIterator(properties.iterator());
+ }
+
+ public Property getProperty(String relPath) {
+ return properties.get(relPath);
+ }
+
+ public PropertyIterator getReferences() {
+ return null;
+ }
+
+ public String getUUID() {
+ return null;
+ }
+
+ public VersionHistory getVersionHistory() {
+ return null;
+ }
+
+ public boolean hasNode(String relPath) {
+ return false;
+ }
+
+ public boolean hasNodes() {
+ return false;
+ }
+
+ public boolean hasProperties() {
+ return false;
+ }
+
+ public boolean hasProperty(String relPath) {
+ return properties.containsKey(relPath);
+ }
+
+ public boolean holdsLock() {
+ return false;
+ }
+
+ public boolean isCheckedOut() {
+ return false;
+ }
+
+ public boolean isLocked() {
+ return false;
+ }
+
+ public boolean isNodeType(String nodeTypeName) {
+ return false;
+ }
+
+ public Lock lock(boolean isDeep, boolean isSessionScoped) {
+ return null;
+ }
+
+ public NodeIterator merge(String srcWorkspace, boolean bestEffort) {
+ return null;
+ }
+
+ public void orderBefore(String srcChildRelPath, String destChildRelPath) {
+ }
+
+ public void removeMixin(String mixinName) {
+ }
+
+ public void restore(String versionName, boolean removeExisting) {
+ }
+
+ public void restore(Version version, boolean removeExisting) {
+ }
+
+ public void restore(Version version, String relPath, boolean removeExisting) {
+ }
+
+ public void restoreByLabel(String versionLabel, boolean removeExisting) {
+ }
+
+ public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(values);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(values);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+ MockProperty p = new MockProperty(name);
+ p.setValue(value);
+ properties.put(name, p);
+ return p;
+ }
+
+ public Property setProperty(String name, Node value) {
+ return null;
+ }
+
+ public Property setProperty(String name, Value value, int type) {
+ return null;
+ }
+
+ public Property setProperty(String name, Value[] values, int type) {
+ return null;
+ }
+
+ public Property setProperty(String name, String[] values, int type) {
+ return null;
+ }
+
+ public Property setProperty(String name, String value, int type) {
+ return null;
+ }
+
+ public void unlock() {
+ }
+
+ public void update(String srcWorkspaceName) {
+ }
+
+ public void accept(ItemVisitor visitor) {
+ }
+
+ public Item getAncestor(int depth) {
+ return null;
+ }
+
+ public int getDepth() {
+ return 0;
+ }
+
+ public Session getSession() {
+ return this.session;
+ }
+
+ public void setSession(Session session) {
+ this.session = session;
+ }
+
+ public boolean isModified() {
+ return false;
+ }
+
+ public boolean isNew() {
+ return false;
+ }
+
+ public boolean isNode() {
+ return true;
+ }
+
+ public void refresh(boolean keepChanges) {
+ }
+
+ public void remove() {
+ }
+
+ public void save() {
+ }
+
+ // JCR 2.0 methods
+
+ public void followLifecycleTransition(String transition)
+ throws UnsupportedRepositoryOperationException,
+ InvalidLifecycleTransitionException, RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public String[] getAllowedLifecycleTransistions()
+ throws UnsupportedRepositoryOperationException, RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String getIdentifier() throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public PropertyIterator getProperties(String[] nameGlobs)
+ throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public PropertyIterator getReferences(String name)
+ throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public NodeIterator getSharedSet() throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public PropertyIterator getWeakReferences() throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public PropertyIterator getWeakReferences(String name)
+ throws RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void removeShare() throws VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void removeSharedSet() throws VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setPrimaryType(String nodeTypeName)
+ throws NoSuchNodeTypeException, VersionException,
+ ConstraintViolationException, LockException, RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public Property setProperty(String name, BigDecimal value)
+ throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Property setProperty(String name, Binary value)
+ throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeIterator.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeIterator.java
new file mode 100644
index 0000000..fff3dba
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeIterator.java
@@ -0,0 +1,73 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.util.NoSuchElementException;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+public class MockNodeIterator implements NodeIterator {
+
+ private Node[] nodes;
+ private int idx;
+ private static final Node [] EMPTY_NODE_ARRAY = {};
+
+ public MockNodeIterator() {
+ this(EMPTY_NODE_ARRAY);
+ }
+
+ public MockNodeIterator(Node[] nodes) {
+ this.nodes = (nodes != null) ? nodes : new Node[0];
+ this.idx = 0;
+ }
+
+ public Node nextNode() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ return nodes[idx++];
+ }
+
+ public long getPosition() {
+ return idx-1;
+ }
+
+ public long getSize() {
+ return nodes.length;
+ }
+
+ public void skip(long skipNum) {
+ idx += skipNum;
+ }
+
+ public boolean hasNext() {
+ return idx < nodes.length;
+ }
+
+ public Object next() {
+ return nextNode();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeType.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeType.java
new file mode 100644
index 0000000..17405b7
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockNodeType.java
@@ -0,0 +1,135 @@
+/*
+ * 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.commons.testing.jcr;
+
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.nodetype.PropertyDefinition;
+
+public class MockNodeType implements NodeType {
+
+ private String name;
+
+ public MockNodeType(String name) {
+ this.name = (name == null) ? "nt:unstructured" : name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean canAddChildNode(String childNodeName) {
+ return false;
+ }
+
+ public boolean canAddChildNode(String childNodeName, String nodeTypeName) {
+ return false;
+ }
+
+ public boolean canRemoveItem(String itemName) {
+ return false;
+ }
+
+ public boolean canSetProperty(String propertyName, Value value) {
+ return false;
+ }
+
+ public boolean canSetProperty(String propertyName, Value[] values) {
+ return false;
+ }
+
+ public NodeDefinition[] getChildNodeDefinitions() {
+ return null;
+ }
+
+ public NodeDefinition[] getDeclaredChildNodeDefinitions() {
+ return null;
+ }
+
+ public PropertyDefinition[] getDeclaredPropertyDefinitions() {
+ return null;
+ }
+
+ public NodeType[] getDeclaredSupertypes() {
+ return null;
+ }
+
+ public String getPrimaryItemName() {
+ return null;
+ }
+
+ public PropertyDefinition[] getPropertyDefinitions() {
+ return null;
+ }
+
+ public NodeType[] getSupertypes() {
+ return null;
+ }
+
+ public boolean hasOrderableChildNodes() {
+ return false;
+ }
+
+ public boolean isMixin() {
+ return false;
+ }
+
+ public boolean isNodeType(String nodeTypeName) {
+ return false;
+ }
+
+ // JCR 2.0 methods
+
+ public boolean canRemoveNode(String nodeName) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean canRemoveProperty(String propertyName) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public NodeTypeIterator getDeclaredSubtypes() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public NodeTypeIterator getSubtypes() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String[] getDeclaredSupertypeNames() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean isAbstract() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public boolean isQueryable() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockProperty.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockProperty.java
new file mode 100644
index 0000000..d9866fb
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockProperty.java
@@ -0,0 +1,287 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Binary;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.version.VersionException;
+
+public class MockProperty implements Property {
+
+ private Value [] values = {};
+ private final String name;
+
+ public MockProperty(String name) {
+ this.name = name;
+ }
+
+ public boolean getBoolean() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0].getBoolean();
+ }
+
+ public Calendar getDate() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0].getDate();
+ }
+
+ public PropertyDefinition getDefinition() throws RepositoryException {
+ return new MockPropertyDefinition(values.length > 1);
+ }
+
+ public double getDouble() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0].getDouble();
+ }
+
+ public long getLength() throws ValueFormatException, RepositoryException {
+ return 0;
+ }
+
+ public long[] getLengths() throws ValueFormatException, RepositoryException {
+ return null;
+ }
+
+ public long getLong() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0].getLong();
+ }
+
+ public Node getNode() throws ValueFormatException, RepositoryException {
+ return null;
+ }
+
+ public InputStream getStream() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0].getStream();
+ }
+
+ public String getString() throws ValueFormatException, RepositoryException {
+ if(values.length > 0) return values[0].getString();
+ return null;
+ }
+
+ public int getType() throws RepositoryException {
+ return PropertyType.STRING;
+ }
+
+ public Value getValue() throws ValueFormatException, RepositoryException {
+ if (values.length > 1) {
+ throw new ValueFormatException();
+ }
+ return values[0];
+ }
+
+ public Value[] getValues() throws ValueFormatException, RepositoryException {
+ return values;
+ }
+
+ public void setValue(boolean value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ MockValue val = new MockValue();
+ val.setValue(value);
+ values = new MockValue[1];
+ values[0] = val;
+ }
+
+ public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ MockValue val = new MockValue();
+ val.setValue(value);
+ values = new MockValue[1];
+ values[0] = val;
+ }
+
+ public void setValue(double value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ MockValue val = new MockValue();
+ val.setValue(value);
+ values = new MockValue[1];
+ values[0] = val;
+ }
+
+ public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ MockValue val = new MockValue();
+ val.setValue(value);
+ values = new MockValue[1];
+ values[0] = val;
+ }
+
+ public void setValue(long value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ MockValue val = new MockValue();
+ val.setValue(value);
+ values = new MockValue[1];
+ values[0] = val;
+ }
+
+ public void setValue(Node value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ }
+
+ public void setValue(String value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ values =new MockValue[1];
+ values[0] = new MockValue(value);
+ }
+
+ public void setValue(String[] inputValues) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ this.values = new MockValue[inputValues.length];
+ int i = 0;
+ for(String str : inputValues) {
+ values[i++] = new MockValue(str);
+ }
+ }
+
+ public void setValue(Value value) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ values = new Value[1];
+ values[0] = value;
+ }
+
+ public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException,
+ ConstraintViolationException, RepositoryException {
+ this.values = values;
+ }
+
+ public void accept(ItemVisitor visitor) throws RepositoryException {
+ }
+
+ public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+ return null;
+ }
+
+ public int getDepth() throws RepositoryException {
+ return 0;
+ }
+
+ public String getName() throws RepositoryException {
+ return name;
+ }
+
+ public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+ return null;
+ }
+
+ public String getPath() throws RepositoryException {
+ return null;
+ }
+
+ public Session getSession() throws RepositoryException {
+ return null;
+ }
+
+ public boolean isModified() {
+ return false;
+ }
+
+ public boolean isNew() {
+ return false;
+ }
+
+ public boolean isNode() {
+ return false;
+ }
+
+ public boolean isSame(Item otherItem) throws RepositoryException {
+ return false;
+ }
+
+ public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException {
+ }
+
+ public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException {
+ }
+
+ public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
+ InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException,
+ NoSuchNodeTypeException, RepositoryException {
+ }
+
+ // JCR 2.0 methods
+
+ public Binary getBinary() throws ValueFormatException, RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public BigDecimal getDecimal() throws ValueFormatException,
+ RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Property getProperty() throws ItemNotFoundException,
+ ValueFormatException, RepositoryException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean isMultiple() throws RepositoryException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public void setValue(BigDecimal value) throws ValueFormatException,
+ VersionException, LockException, ConstraintViolationException,
+ RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setValue(Binary value) throws ValueFormatException,
+ VersionException, LockException, ConstraintViolationException,
+ RepositoryException {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyDefinition.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyDefinition.java
new file mode 100644
index 0000000..864f961
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyDefinition.java
@@ -0,0 +1,87 @@
+/*
+ * 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.commons.testing.jcr;
+
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+
+public class MockPropertyDefinition implements PropertyDefinition {
+
+ private boolean multiple;
+
+ public MockPropertyDefinition(boolean multiple) {
+ this.multiple = multiple;
+
+ }
+ public Value[] getDefaultValues() {
+ return null;
+ }
+
+ public int getRequiredType() {
+ return 0;
+ }
+
+ public String[] getValueConstraints() {
+ return null;
+ }
+
+ public boolean isMultiple() {
+ return multiple;
+ }
+
+ public NodeType getDeclaringNodeType() {
+ return null;
+ }
+
+ public String getName() {
+ return null;
+ }
+
+ public int getOnParentVersion() {
+ return 0;
+ }
+
+ public boolean isAutoCreated() {
+ return false;
+ }
+
+ public boolean isMandatory() {
+ return false;
+ }
+
+ public boolean isProtected() {
+ return false;
+ }
+
+ // JCR 2.0 methods
+
+ public String[] getAvailableQueryOperators() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ public boolean isFullTextSearchable() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+ public boolean isQueryOrderable() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyIterator.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyIterator.java
new file mode 100644
index 0000000..c141af4
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockPropertyIterator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.util.Iterator;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+
+public class MockPropertyIterator implements PropertyIterator {
+
+ private final Iterator<Property> iterator;
+ int index = 0;
+
+ public MockPropertyIterator(Iterator <Property> it) {
+ iterator = it;
+ }
+
+ public Property nextProperty() {
+ return iterator.next();
+ }
+
+ public long getPosition() {
+ return -1;
+ }
+
+ public long getSize() {
+ return -1;
+ }
+
+ public void skip(long skipNum) {
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public Object next() {
+ return nextProperty();
+ }
+
+ public void remove() {
+ throw new Error("Not implemented: remove");
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockValue.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockValue.java
new file mode 100644
index 0000000..8a1774b
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/MockValue.java
@@ -0,0 +1,114 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Binary;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+public class MockValue implements Value {
+
+ private String stringValue;
+ private boolean booleanValue;
+ private Calendar calendarValue;
+ private double doubleValue;
+ private long longValue;
+ private InputStream stream;
+ private int propertyType;
+ private BigDecimal decimal;
+
+ public MockValue() {
+ }
+
+ public MockValue(String str) {
+ stringValue = str;
+ propertyType = PropertyType.STRING;
+ }
+
+ public boolean getBoolean() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return booleanValue;
+ }
+
+ public Calendar getDate() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return calendarValue;
+ }
+
+ public double getDouble() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return doubleValue;
+ }
+
+ public long getLong() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return longValue;
+ }
+
+ public InputStream getStream() throws IllegalStateException, RepositoryException {
+ return stream;
+ }
+
+ public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return stringValue;
+ }
+
+ public int getType() {
+ return propertyType;
+ }
+
+
+ public void setValue(String stringValue) {
+ this.stringValue = stringValue;
+ }
+
+ public void setValue(boolean booleanValue) {
+ this.booleanValue = booleanValue;
+ }
+
+ public void setValue(Calendar calendarValue) {
+ this.calendarValue = calendarValue;
+ }
+
+ public void setValue(double doubleValue) {
+ this.doubleValue = doubleValue;
+ }
+
+ public void setValue(long longValue) {
+ this.longValue = longValue;
+ }
+
+ public void setValue(InputStream stream) {
+ this.stream = stream;
+ }
+
+ public void setDecimal(BigDecimal value) {
+ this.decimal = value;
+ }
+
+ public Binary getBinary() throws RepositoryException {
+ return null;
+ }
+
+ public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+ return decimal;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryProvider.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryProvider.java
new file mode 100644
index 0000000..1ab2742
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryProvider.java
@@ -0,0 +1,65 @@
+/*
+ * 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.commons.testing.jcr;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.jcr.api.SlingRepository;
+
+/** Provide a Repository to test classes, on-demand,
+ * with auto-cleanup at JVM shutdown.
+ */
+public class RepositoryProvider {
+ private static RepositoryProvider INSTANCE;
+ private SlingRepository repository;
+
+ private static class ShutdownThread extends Thread {
+ @Override
+ public void run() {
+ try {
+ RepositoryUtil.stopRepository();
+ } catch(Exception e) {
+ System.out.println("Exception in ShutdownThread:" + e);
+ }
+ }
+
+ };
+
+ private RepositoryProvider() {
+ }
+
+ public synchronized static RepositoryProvider instance() {
+ if(INSTANCE == null) {
+ INSTANCE = new RepositoryProvider();
+ }
+ return INSTANCE;
+ }
+
+ /** Return a SlingRepository. First call initializes it, and a JVM
+ * shutdown hook is registered to stop it.
+ **/
+ public synchronized SlingRepository getRepository() throws RepositoryException {
+ if(repository == null) {
+ RepositoryUtil.startRepository();
+ repository = RepositoryUtil.getRepository();
+ Runtime.getRuntime().addShutdownHook(new ShutdownThread());
+ }
+ return repository;
+ }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryTestBase.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryTestBase.java
new file mode 100644
index 0000000..3c990b4
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryTestBase.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.testing.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.naming.NamingException;
+
+import junit.framework.TestCase;
+
+import org.apache.sling.jcr.api.SlingRepository;
+
+/** Base class for JUnit3-style tests which need a Repository.
+ * Should eventually be deprecated in favor of {@link RepositoryProvider}
+ * which is less intrusive
+ */
+public class RepositoryTestBase extends TestCase {
+ protected Node testRoot;
+ protected Session session;
+ private int counter;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if(session != null) {
+ session.logout();
+ }
+ }
+
+ /** Return a JCR Session, initialized on demand */
+ protected Session getSession() throws RepositoryException, NamingException {
+ if(session == null) {
+ session = getRepository().loginAdministrative(null);
+ }
+ return session;
+ }
+
+ /** Return a test root node, created on demand, with a unique path */
+ protected Node getTestRootNode() throws RepositoryException, NamingException {
+ if(testRoot==null) {
+ final Node root = getSession().getRootNode();
+ final Node classRoot = root.addNode(getClass().getSimpleName());
+ testRoot = classRoot.addNode(System.currentTimeMillis() + "_" + (++counter));
+ }
+ return testRoot;
+ }
+
+ /** Return a Repository */
+ protected SlingRepository getRepository() throws RepositoryException, NamingException {
+ return RepositoryProvider.instance().getRepository();
+ }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryUtil.java b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryUtil.java
new file mode 100644
index 0000000..1981bfc
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/jcr/RepositoryUtil.java
@@ -0,0 +1,230 @@
+/*
+ * 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.commons.testing.jcr;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.naming.NamingException;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.sling.jcr.api.SlingRepository;
+
+/**
+ * Utility class for managing JCR repositories, used to initialize temporary
+ * Jackrabbit repositories for testing.
+ */
+public class RepositoryUtil {
+
+ public static final String ADMIN_NAME = "admin";
+
+ public static final String ADMIN_PASSWORD = "admin";
+
+ public static final String HOME_DIR = "target/repository";
+
+ public static final String CONFIG_FILE = "jackrabbit-test-config.xml";
+
+ private static SlingRepository repository;
+
+ private static Session adminSession;
+
+ /**
+ * Start a new repository
+ *
+ * @throws RepositoryException when it is not possible to start the
+ * repository.
+ */
+ public static void startRepository() throws RepositoryException {
+ if ( adminSession == null ) {
+ // copy the repository configuration file to the repository HOME_DIR
+ InputStream ins = RepositoryUtil.class.getClassLoader().getResourceAsStream(
+ CONFIG_FILE);
+ if (ins == null) {
+ throw new RepositoryException("Cannot get " + CONFIG_FILE);
+ }
+
+ File configFile = new File(HOME_DIR, "repository.xml");
+ configFile.getParentFile().mkdirs();
+
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(configFile);
+ byte[] buf = new byte[1024];
+ int rd;
+ while ((rd = ins.read(buf)) >= 0) {
+ out.write(buf, 0, rd);
+ }
+ } catch (IOException ioe) {
+ throw new RepositoryException("Cannot copy configuration file to "
+ + configFile);
+ } finally {
+ try {
+ ins.close();
+ } catch (IOException ignore) {
+ }
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ // somewhat dirty hack to have the derby.log file in a sensible
+ // location, but don't overwrite anything already set
+ if (System.getProperty("derby.stream.error.file") == null) {
+ String derbyLog = HOME_DIR + "/derby.log";
+ System.setProperty("derby.stream.error.file", derbyLog);
+ }
+
+ final File f = new File(HOME_DIR);
+ repository = new RepositoryWrapper(JcrUtils.getRepository(f.toURI().toString()));
+ adminSession = repository.loginAdministrative(null);
+ }
+ }
+
+ /**
+ * Stop a repository.
+ */
+ public static void stopRepository() throws NamingException {
+ if ( adminSession != null ) {
+ adminSession.logout();
+ adminSession = null;
+ repository = null;
+ }
+ }
+
+ /**
+ * Get a repository
+ *
+ * @return a JCR repository reference
+ */
+ public static SlingRepository getRepository() {
+ return repository;
+ }
+
+ /**
+ * Registers node types from the CND file read from the <code>source</code>
+ * with the node type manager available from the given <code>session</code>.
+ * <p>
+ * This method is not synchronized. It is up to the calling method to
+ * prevent paralell execution.
+ *
+ * @param session The <code>Session</code> providing the node type manager
+ * through which the node type is to be registered.
+ * @param source The <code>InputStream</code> from which the CND file is
+ * read.
+ * @return <code>true</code> if registration of all node types succeeded.
+ */
+ public static boolean registerNodeType(Session session, InputStream source)
+ throws IOException, RepositoryException {
+ try {
+ CndImporter.registerNodeTypes(new InputStreamReader(source, "UTF-8"), session);
+ return true;
+ } catch (Exception e) {
+ // ignore
+ return false;
+ }
+ }
+
+ public static void registerSlingNodeTypes(Session adminSession) throws IOException, RepositoryException {
+ final Class<RepositoryUtil> clazz = RepositoryUtil.class;
+ registerNodeType(adminSession,
+ clazz.getResourceAsStream("/SLING-INF/nodetypes/folder.cnd"));
+ RepositoryUtil.registerNodeType(adminSession,
+ clazz.getResourceAsStream("/SLING-INF/nodetypes/resource.cnd"));
+ RepositoryUtil.registerNodeType(adminSession,
+ clazz.getResourceAsStream("/SLING-INF/nodetypes/vanitypath.cnd"));
+ }
+
+ public static final class RepositoryWrapper implements SlingRepository {
+
+ protected final Repository wrapped;
+
+ public RepositoryWrapper(Repository r) {
+ wrapped = r;
+ }
+
+ public String getDescriptor(String key) {
+ return wrapped.getDescriptor(key);
+ }
+
+ public String[] getDescriptorKeys() {
+ return wrapped.getDescriptorKeys();
+ }
+
+ public Session login() throws LoginException, RepositoryException {
+ return wrapped.login();
+ }
+
+ public Session login(Credentials credentials, String workspaceName)
+ throws LoginException, NoSuchWorkspaceException,
+ RepositoryException {
+ return wrapped.login(credentials, (workspaceName == null ? getDefaultWorkspace() : workspaceName));
+ }
+
+ public Session login(Credentials credentials) throws LoginException,
+ RepositoryException {
+ return wrapped.login(credentials);
+ }
+
+ public Session login(String workspaceName) throws LoginException,
+ NoSuchWorkspaceException, RepositoryException {
+ return wrapped.login((workspaceName == null ? getDefaultWorkspace() : workspaceName));
+ }
+
+ public String getDefaultWorkspace() {
+ return "default";
+ }
+
+ public Session loginAdministrative(String workspaceName)
+ throws RepositoryException {
+ final Credentials credentials = new SimpleCredentials(ADMIN_NAME,
+ ADMIN_PASSWORD.toCharArray());
+ return this.login(credentials, (workspaceName == null ? getDefaultWorkspace() : workspaceName));
+ }
+
+ public Value getDescriptorValue(String key) {
+ return wrapped.getDescriptorValue(key);
+ }
+
+ public Value[] getDescriptorValues(String key) {
+ return wrapped.getDescriptorValues(key);
+ }
+
+ public boolean isSingleValueDescriptor(String key) {
+ return wrapped.isSingleValueDescriptor(key);
+ }
+
+ public boolean isStandardDescriptor(String key) {
+ return wrapped.isStandardDescriptor(key);
+ }
+
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/junit/Retry.java b/testing/src/main/java/org/apache/sling/commons/testing/junit/Retry.java
new file mode 100644
index 0000000..219547a
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/junit/Retry.java
@@ -0,0 +1,33 @@
+/*
+ * 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.commons.testing.junit;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Used to annotate JUnit tests as being retryable */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Retry {
+
+ /** Retries for at most this many milliseconds */
+ int timeoutMsec() default -1;
+
+ /** Wait this many milliseconds between retries */
+ int intervalMsec() default -1;
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/junit/RetryRule.java b/testing/src/main/java/org/apache/sling/commons/testing/junit/RetryRule.java
new file mode 100644
index 0000000..d2d13ad
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/junit/RetryRule.java
@@ -0,0 +1,112 @@
+/*
+ * 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.commons.testing.junit;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** JUnit Rule that implements retries, see tests for usage example */
+public class RetryRule implements TestRule {
+
+ public static final int DEFAULT_DEFAULT_TIMEOUT_MSEC = 5000;
+ public static final int DEFAULT_DEFAULT_INTERVAL_MSEC = 500;
+
+ private static final Logger log = LoggerFactory.getLogger(RetryRule.class);
+
+ private final long defaultTimeout;
+ private final long defaultInterval;
+
+ /** Create a RetryRule with default values for default timeout and interval */
+ public RetryRule() {
+ this(DEFAULT_DEFAULT_TIMEOUT_MSEC, DEFAULT_DEFAULT_INTERVAL_MSEC);
+ }
+
+ /** Create a RetryRule with specific values for default timeout and interval */
+ public RetryRule(long defaultTimeout, long defaultInterval) {
+ this.defaultTimeout = defaultTimeout;
+ this.defaultInterval = defaultInterval;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append(getClass().getSimpleName())
+ .append(", default interval=")
+ .append(defaultInterval)
+ .append(" msec, default timeout=")
+ .append(defaultTimeout)
+ .append(" msec")
+ .toString();
+ }
+
+ public Statement apply(final Statement statement, final Description description) {
+ return new Statement() {
+
+ private Throwable eval(Statement stmt) {
+ try {
+ stmt.evaluate();
+ } catch(Throwable t) {
+ return t;
+ }
+ return null;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ int retries = 0;
+ Throwable t = eval(statement);
+ if(t != null) {
+ final Retry r = description.getAnnotation(Retry.class);
+ if(r != null) {
+ final long timeout = System.currentTimeMillis() + getTimeout(r.timeoutMsec());
+ while(System.currentTimeMillis() < timeout) {
+ retries++;
+ t = eval(statement);
+ if(t == null) {
+ break;
+ }
+ Thread.sleep(getInterval(r.intervalMsec()));
+ }
+ }
+ }
+ if(t != null) {
+ if(retries > 0) {
+ log.debug("{} fails after retrying {} time(s)", statement, retries);
+ }
+ throw t;
+ }
+ if(retries > 0) {
+ log.debug("{} succeeds after retrying {} time(s)", statement, retries);
+ }
+ }
+ };
+ }
+
+ long getTimeout(long ruleTimeout) {
+ return ruleTimeout > 0 ? ruleTimeout : defaultTimeout;
+ }
+
+ long getInterval(long ruleInterval) {
+ return ruleInterval > 0 ? ruleInterval : defaultInterval;
+ }
+
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/JackrabbitOnly.java b/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/JackrabbitOnly.java
new file mode 100644
index 0000000..5ffae5b
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/JackrabbitOnly.java
@@ -0,0 +1,23 @@
+/*
+ * 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.commons.testing.junit.categories;
+
+/** JUnit Category for tests that run on Jackrabbit only */
+public interface JackrabbitOnly {
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/OakOnly.java b/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/OakOnly.java
new file mode 100644
index 0000000..58354bc
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/junit/categories/OakOnly.java
@@ -0,0 +1,23 @@
+/*
+ * 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.commons.testing.junit.categories;
+
+/** JUnit Category for tests that run on Oak only */
+public interface OakOnly {
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundle.java b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundle.java
new file mode 100644
index 0000000..0188162
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundle.java
@@ -0,0 +1,143 @@
+/*
+ * 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.commons.testing.osgi;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+public class MockBundle implements Bundle {
+
+ private long bundleId;
+
+ public MockBundle(long bundleId) {
+ this.bundleId = bundleId;
+ }
+
+ public long getBundleId() {
+ return bundleId;
+ }
+
+ public Enumeration<?> findEntries(String path, String filePattern,
+ boolean recurse) {
+ return null;
+ }
+
+ public URL getEntry(String name) {
+ return null;
+ }
+
+ public Enumeration<?> getEntryPaths(String path) {
+ return null;
+ }
+
+ public Dictionary<?, ?> getHeaders() {
+ return null;
+ }
+
+ public Dictionary<?, ?> getHeaders(String locale) {
+ return null;
+ }
+
+ public long getLastModified() {
+ return 0;
+ }
+
+ public String getLocation() {
+ return null;
+ }
+
+ public ServiceReference[] getRegisteredServices() {
+ return null;
+ }
+
+ public URL getResource(String name) {
+ return null;
+ }
+
+ public Enumeration<?> getResources(String name) {
+ return null;
+ }
+
+ public ServiceReference[] getServicesInUse() {
+ return null;
+ }
+
+ public int getState() {
+ return 0;
+ }
+
+ public String getSymbolicName() {
+ return null;
+ }
+
+ public boolean hasPermission(Object permission) {
+ return false;
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException(name);
+ }
+
+ public void start() {
+
+ }
+
+ public void stop() {
+
+ }
+
+ public void uninstall() {
+
+ }
+
+ public void update() {
+
+ }
+
+ public void update(InputStream in) {
+
+ }
+
+ public BundleContext getBundleContext() {
+ return null;
+ }
+
+ public void start(int options) throws BundleException {
+ }
+
+ public void stop(int options) throws BundleException {
+ }
+
+ public Map getSignerCertificates(int signersType) {
+ return null;
+ }
+
+ public Version getVersion() {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundleContext.java b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundleContext.java
new file mode 100644
index 0000000..168b748
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockBundleContext.java
@@ -0,0 +1,157 @@
+/*
+ * 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.commons.testing.osgi;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public class MockBundleContext implements BundleContext {
+ private MockBundle bundle;
+
+ public MockBundleContext(MockBundle bundle) {
+ this.bundle = bundle;
+ }
+
+ public String getProperty(String s) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+
+ }
+
+ public Bundle installBundle(String s) throws BundleException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Bundle installBundle(String s, InputStream inputStream)
+ throws BundleException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Bundle getBundle(long l) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Bundle[] getBundles() {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void addServiceListener(ServiceListener serviceListener, String s)
+ throws InvalidSyntaxException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void addServiceListener(ServiceListener serviceListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void removeServiceListener(ServiceListener serviceListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void addBundleListener(BundleListener bundleListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void removeBundleListener(BundleListener bundleListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void addFrameworkListener(FrameworkListener frameworkListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void removeFrameworkListener(FrameworkListener frameworkListener) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public ServiceRegistration registerService(String[] strings, Object o,
+ Dictionary dictionary) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public ServiceRegistration registerService(String s, Object o,
+ Dictionary dictionary) {
+ return new MockServiceRegistration();
+ }
+
+ public ServiceReference[] getServiceReferences(String s, String s1)
+ throws InvalidSyntaxException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public ServiceReference[] getAllServiceReferences(String s, String s1)
+ throws InvalidSyntaxException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public ServiceReference getServiceReference(String s) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Object getService(ServiceReference serviceReference) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public boolean ungetService(ServiceReference serviceReference) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public File getDataFile(String s) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Filter createFilter(String s) throws InvalidSyntaxException {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockComponentContext.java b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockComponentContext.java
new file mode 100644
index 0000000..d31993c
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockComponentContext.java
@@ -0,0 +1,116 @@
+/*
+ * 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.commons.testing.osgi;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.Servlet;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.ComponentInstance;
+
+public class MockComponentContext implements ComponentContext {
+
+ private Dictionary<Object, Object> properties = new Properties();
+
+ private MockBundleContext mockBundleContext;
+
+ private Servlet servlet;
+
+ private Map<ServiceReference, Object> services = new HashMap<ServiceReference, Object>();
+
+ public MockComponentContext(MockBundle bundle) {
+ mockBundleContext = new MockBundleContext(bundle);
+ }
+
+ public MockComponentContext(MockBundleContext mockBundleContext) {
+ this.mockBundleContext = mockBundleContext;
+ }
+
+ public MockComponentContext(MockBundle bundle, Servlet servlet) {
+ mockBundleContext = new MockBundleContext(bundle);
+ this.servlet = servlet;
+ }
+
+ public MockComponentContext(MockBundleContext mockBundleContext, Servlet servlet) {
+ this.mockBundleContext = mockBundleContext;
+ this.servlet = servlet;
+ }
+
+ public void addService(ServiceReference reference, Object service) {
+ services.put(reference, service);
+ }
+
+
+ public void setProperty(Object key, Object value) {
+ // noinspection unchecked
+ this.properties.put(key, value);
+ }
+
+ public Dictionary<Object, Object> getProperties() {
+ // noinspection ReturnOfCollectionOrArrayField
+ return this.properties;
+ }
+
+ public Object locateService(String name, ServiceReference reference) {
+ // the constant is from Sling Core, but we don't want to have a dep just because of this
+ String referenceName = (String) reference.getProperty("sling.core.servletName");
+ if (referenceName != null && referenceName.equals(name)) {
+ return this.servlet;
+ }
+
+ return services.get(reference);
+ }
+
+ public BundleContext getBundleContext() {
+ return mockBundleContext;
+ }
+
+ public void disableComponent(String name) {
+ }
+
+ public void enableComponent(String name) {
+ }
+
+ public ComponentInstance getComponentInstance() {
+ return null;
+ }
+
+ public ServiceReference getServiceReference() {
+ return null;
+ }
+
+ public Bundle getUsingBundle() {
+ return null;
+ }
+
+ public Object locateService(String name) {
+ return null;
+ }
+
+ public Object[] locateServices(String name) {
+ return null;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceReference.java b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceReference.java
new file mode 100644
index 0000000..5439b50
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceReference.java
@@ -0,0 +1,73 @@
+/*
+ * 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.commons.testing.osgi;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class MockServiceReference implements ServiceReference {
+
+ private static long serviceIdCounter = 0;
+
+ private Bundle bundle;
+
+ private Dictionary<String, Object> props;
+
+ public MockServiceReference(Bundle bundle) {
+ this.bundle = bundle;
+ this.props = new Hashtable<String, Object>();
+
+ // mockup a service id
+ props.put(Constants.SERVICE_ID, serviceIdCounter++);
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ public void setProperty(String key, Object value) {
+ props.put(key, value);
+ }
+
+ public Object getProperty(String key) {
+ return props.get(key);
+ }
+
+ public String[] getPropertyKeys() {
+ return Collections.list(props.keys()).toArray(new String[props.size()]);
+ }
+
+ public Bundle[] getUsingBundles() {
+ return null;
+ }
+
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ return false;
+ }
+
+ public int compareTo(Object reference) {
+ return 0;
+ }
+
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceRegistration.java b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceRegistration.java
new file mode 100644
index 0000000..70557f1
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/osgi/MockServiceRegistration.java
@@ -0,0 +1,39 @@
+/*
+ * 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.commons.testing.osgi;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+class MockServiceRegistration implements ServiceRegistration {
+
+ public ServiceReference getReference() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public void setProperties(Dictionary dictionary) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public void unregister() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestPathInfo.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestPathInfo.java
new file mode 100644
index 0000000..12e2645
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestPathInfo.java
@@ -0,0 +1,70 @@
+/*
+ * 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.commons.testing.sling;
+
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+
+class MockRequestPathInfo implements RequestPathInfo {
+
+ public Resource getSuffixResource() {
+ return null;
+ }
+
+ private final String selectors;
+
+ private final String extension;
+
+ private final String suffix;
+
+ private final String path;
+
+ public MockRequestPathInfo(String selectors, String extension, String suffix) {
+ this(selectors, extension, suffix, null);
+ }
+
+ public MockRequestPathInfo(String selectors, String extension, String suffix, String path) {
+ this.selectors = selectors;
+ this.extension = extension;
+ this.suffix = suffix;
+ this.path = path;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public String getResourcePath() {
+ return path;
+ }
+
+ public String getSelectorString() {
+ return selectors;
+ }
+
+ public String[] getSelectors() {
+ return (getSelectorString() != null)
+ ? getSelectorString().split("\\.")
+ : new String[0];
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestProgressTracker.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestProgressTracker.java
new file mode 100644
index 0000000..41c43e2
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockRequestProgressTracker.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.testing.sling;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+
+public class MockRequestProgressTracker implements RequestProgressTracker {
+
+ public void dump(PrintWriter writer) {
+ }
+
+ public Iterator<String> getMessages() {
+ return Collections.<String> emptyList().iterator();
+ }
+
+ public void log(String message) {
+ }
+
+ public void log(String format, Object... args) {
+ }
+
+ public void logTimer(String timerName) {
+ }
+
+ public void logTimer(String timerName, String format, Object... args) {
+ }
+
+ public void startTimer(String timerName) {
+ }
+
+ public void done() {
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResource.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResource.java
new file mode 100644
index 0000000..d74d737
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResource.java
@@ -0,0 +1,91 @@
+/*
+ * 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.commons.testing.sling;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockResource extends SyntheticResource {
+
+ private String resourceType;
+ private String resourceSuperType;
+ private Map<String,Object> properties = new HashMap<String,Object>();
+
+ public MockResource(ResourceResolver resourceResolver, String path,
+ String resourceType) {
+ this(resourceResolver, path, resourceType, null);
+ }
+
+ public MockResource(ResourceResolver resourceResolver, String path,
+ String resourceType, String resourceSuperType) {
+ super(resourceResolver, path, resourceType);
+
+ setResourceType(resourceType);
+ setResourceSuperType(resourceSuperType);
+ }
+
+ public void addProperty(String key, Object value){
+ this.properties.put(key,value);
+ }
+
+ public Map<String,Object> getProperties(){
+ return this.properties;
+ }
+
+ @Override
+ public String getResourceType() {
+ return resourceType;
+ }
+
+ public void setResourceType(String resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ @Override
+ public String getResourceSuperType() {
+ return resourceSuperType;
+ }
+
+ public void setResourceSuperType(String resourceSuperType) {
+ this.resourceSuperType = resourceSuperType;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ if (type == ValueMap.class) {
+ ValueMap map = new ValueMapDecorator(new HashMap<String, Object>());
+ if (resourceType != null) {
+ map.put("resourceType", resourceType);
+ }
+ if (resourceSuperType != null) {
+ map.put("resourceSuperType", resourceSuperType);
+ }
+ for (String key : this.properties.keySet()) {
+ map.put(key,this.properties.get(key));
+ }
+ return (AdapterType) map;
+ }
+ throw new UnsupportedOperationException("AdaptTo " + type.getSimpleName() + " not implemented");
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResourceResolver.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResourceResolver.java
new file mode 100644
index 0000000..f0ba860
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockResourceResolver.java
@@ -0,0 +1,230 @@
+/*
+ * 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.commons.testing.sling;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * @deprecated Please use the Mock Resource Resolver Implementation from testing/resourceresolver-mock instead.
+ */
+@Deprecated
+public class MockResourceResolver implements ResourceResolver {
+
+ private String[] searchPath;
+
+ private Map<String, Resource> resources = new LinkedHashMap<String, Resource>();
+
+ private Map<String, Collection<Resource>> children = new LinkedHashMap<String, Collection<Resource>>();
+
+ public void addResource(Resource resource) {
+ this.resources.put(resource.getPath(), resource);
+ }
+
+ public void addChildren(Resource parent, Collection<Resource> children) {
+ this.children.put(parent.getPath(), children);
+ }
+
+ public Resource resolve(HttpServletRequest request) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Resource resolve(String absPath) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public String map(String resourcePath) {
+ return resourcePath; // a rather simplistic 1:1 map...
+
+ }
+
+ public Resource getResource(String path) {
+ return resources.get(path);
+ }
+
+ public Resource getResource(Resource base, String path) {
+ if (!path.startsWith("/")) {
+ path = base.getPath() + "/" + path;
+ }
+ return getResource(path);
+ }
+
+ public String[] getSearchPath() {
+ return searchPath.clone();
+
+ }
+
+ public Iterator<Resource> listChildren(final Resource parent) {
+ Collection<Resource> childCollection = children.get(parent.getPath());
+ if (childCollection != null) {
+ return childCollection.iterator();
+ }
+
+ return new Iterator<Resource>() {
+ final String parentPath = parent.getPath() + "/";
+
+ final Iterator<Resource> elements = resources.values().iterator();
+
+ Resource nextResource = seek();
+
+ public boolean hasNext() {
+ return nextResource != null;
+ }
+
+ public Resource next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ Resource result = nextResource;
+ nextResource = seek();
+ return result;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private Resource seek() {
+ while (elements.hasNext()) {
+ Resource next = elements.next();
+ String path = next.getPath();
+ if (path.startsWith(parentPath)
+ && path.indexOf('/', parentPath.length()) < 0) {
+ return next;
+ }
+ }
+ return null;
+ }
+ };
+ }
+
+ public Iterator<Resource> findResources(String query, String language) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public Iterator<Map<String, Object>> queryResources(String query,
+ String language) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ throw new UnsupportedOperationException("Not implemented");
+
+ }
+
+ public void setSearchPath(String... searchPath) {
+ if (searchPath == null) {
+ this.searchPath = new String[0];
+ } else {
+ this.searchPath = new String[searchPath.length];
+ for (int i=0; i < searchPath.length; i++) {
+ String entry = searchPath[i];
+ if (!entry.endsWith("/")) {
+ entry = entry.concat("/");
+ }
+ this.searchPath[i] = entry;
+ }
+ }
+ }
+
+ public String map(HttpServletRequest request, String resourcePath) {
+ return request.getContextPath() + resourcePath;
+ }
+
+ public Resource resolve(HttpServletRequest request, String absPath) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public void close() {
+ // nothing to do
+ }
+
+ public String getUserID() {
+ return null;
+ }
+
+ public boolean isLive() {
+ return true;
+ }
+
+ public ResourceResolver clone(Map<String, Object> authenticationInfo)
+ throws LoginException {
+ return null;
+ }
+
+ public Object getAttribute(String name) {
+ return null;
+ }
+
+ public Iterator<String> getAttributeNames() {
+ return null;
+ }
+
+ public void commit() throws PersistenceException {
+ }
+
+ public Resource create(Resource arg0, String arg1, Map<String, Object> arg2)
+ throws PersistenceException {
+ return null;
+ }
+
+ public void delete(Resource arg0) throws PersistenceException {
+ }
+
+ public Iterable<Resource> getChildren(Resource arg0) {
+ return null;
+ }
+
+ public String getParentResourceType(Resource arg0) {
+ return null;
+ }
+
+ public String getParentResourceType(String arg0) {
+ return null;
+ }
+
+ public boolean hasChanges() {
+ return false;
+ }
+
+ public boolean isResourceType(Resource arg0, String arg1) {
+ return false;
+ }
+
+ public void refresh() {
+ }
+
+ public void revert() {
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletRequest.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletRequest.java
new file mode 100644
index 0000000..3516fe5
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletRequest.java
@@ -0,0 +1,403 @@
+/*
+ * 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.commons.testing.sling;
+
+import java.io.BufferedReader;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
+
+/**
+ * Mock request object. This does not do anything useful, it just returns the
+ * constructor parameter <code>secure</code> in the <code>isSecure</code>
+ * method.
+ */
+public class MockSlingHttpServletRequest implements SlingHttpServletRequest {
+
+ private Resource resource;
+
+ private String method;
+
+ private final RequestPathInfo requestPathInfo;
+
+ private final String queryString;
+
+ private final String scheme;
+ private final String server;
+ private final int port;
+ private final String contextPath;
+
+ private boolean secure = false;
+
+ private ResourceResolver mockResourceResolver;
+
+ private RequestProgressTracker mockRequestProgressTracker;
+
+ public static final String RESOURCE_TYPE = "foo/bar";
+
+ MockSlingHttpServletRequest() {
+ this(null, null, null, null, null);
+ }
+
+ public MockSlingHttpServletRequest(String resourcePath, String selectors,
+ String extension, String suffix, String queryString) {
+ this(resourcePath, selectors, extension, suffix, queryString,
+ resourcePath, null, null, 0, null);
+ }
+
+ public MockSlingHttpServletRequest(String resourcePath, String selectors,
+ String extension, String suffix, String queryString,
+ String requestPath, String scheme, String server, int port,
+ String contextPath) {
+ this.resource = new SyntheticResource(null, resourcePath, RESOURCE_TYPE);
+ this.requestPathInfo = new MockRequestPathInfo(selectors, extension,
+ suffix, requestPath);
+ this.queryString = queryString;
+ this.scheme = scheme;
+ this.server = server;
+ this.port = port;
+ this.contextPath = contextPath;
+
+ setMethod(null);
+ }
+
+ public void setResourceResolver(ResourceResolver resolver) {
+ this.mockResourceResolver = resolver;
+
+ // recreate request resource with the new resolver
+ if (resource.getResourceResolver() == null) {
+ this.resource = new SyntheticResource(resolver, resource.getPath(),
+ resource.getResourceType());
+ }
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ public void setSecure(boolean secure) {
+ this.secure = secure;
+ }
+
+ public void setMethod(String method) {
+ this.method = (method == null) ? "GET" : method.toUpperCase();
+ }
+
+ public Cookie getCookie(String name) {
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path,
+ RequestDispatcherOptions options) {
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(Resource resource,
+ RequestDispatcherOptions options) {
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(Resource resource) {
+ return null;
+ }
+
+ public RequestParameter getRequestParameter(String name) {
+ return null;
+ }
+
+ public RequestParameterMap getRequestParameterMap() {
+ return null;
+ }
+
+ public RequestParameter[] getRequestParameters(String name) {
+ return null;
+ }
+
+ public RequestPathInfo getRequestPathInfo() {
+ return requestPathInfo;
+ }
+
+ public RequestProgressTracker getRequestProgressTracker() {
+ if (mockRequestProgressTracker == null) {
+ mockRequestProgressTracker = new MockRequestProgressTracker();
+ }
+ return mockRequestProgressTracker;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public ResourceBundle getResourceBundle(Locale locale) {
+ return null;
+ }
+
+ public ResourceBundle getResourceBundle(String baseName, Locale locale) {
+ return null;
+ }
+
+ public ResourceResolver getResourceResolver() {
+ return mockResourceResolver;
+ }
+
+ public String getResponseContentType() {
+ return null;
+ }
+
+ public Enumeration<String> getResponseContentTypes() {
+ return null;
+ }
+
+ public String getAuthType() {
+ return null;
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ public Cookie[] getCookies() {
+ return null;
+ }
+
+ public long getDateHeader(String name) {
+ return 0;
+ }
+
+ public String getHeader(String name) {
+ return null;
+ }
+
+ public Enumeration<?> getHeaderNames() {
+ return null;
+ }
+
+ public Enumeration<?> getHeaders(String name) {
+ return null;
+ }
+
+ public int getIntHeader(String name) {
+ return 0;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public String getPathInfo() {
+ return null;
+ }
+
+ public String getPathTranslated() {
+ return null;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+
+ public String getRemoteUser() {
+ return null;
+ }
+
+ public String getRequestURI() {
+ return null;
+ }
+
+ public StringBuffer getRequestURL() {
+ return null;
+ }
+
+ public String getRequestedSessionId() {
+ return null;
+ }
+
+ public String getServletPath() {
+ return null;
+ }
+
+ public HttpSession getSession() {
+ return null;
+ }
+
+ public HttpSession getSession(boolean create) {
+ return null;
+ }
+
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ public boolean isRequestedSessionIdFromCookie() {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdFromURL() {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdFromUrl() {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdValid() {
+ return false;
+ }
+
+ public boolean isUserInRole(String role) {
+ return false;
+ }
+
+ public Object getAttribute(String name) {
+ return null;
+ }
+
+ public Enumeration<?> getAttributeNames() {
+ return null;
+ }
+
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ public int getContentLength() {
+ return 0;
+ }
+
+ public String getContentType() {
+ return null;
+ }
+
+ public ServletInputStream getInputStream() {
+ return null;
+ }
+
+ public String getLocalAddr() {
+ return null;
+ }
+
+ public String getLocalName() {
+ return null;
+ }
+
+ public int getLocalPort() {
+ return 0;
+ }
+
+ public Locale getLocale() {
+ return null;
+ }
+
+ public Enumeration<?> getLocales() {
+ return null;
+ }
+
+ public String getParameter(String name) {
+ return null;
+ }
+
+ public Map<?, ?> getParameterMap() {
+ return null;
+ }
+
+ public Enumeration<?> getParameterNames() {
+ return null;
+ }
+
+ public String[] getParameterValues(String name) {
+ return null;
+ }
+
+ public String getProtocol() {
+ return null;
+ }
+
+ public BufferedReader getReader() {
+ return null;
+ }
+
+ public String getRealPath(String path) {
+ return null;
+ }
+
+ public String getRemoteAddr() {
+ return null;
+ }
+
+ public String getRemoteHost() {
+ return null;
+ }
+
+ public int getRemotePort() {
+ return 0;
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return null;
+ }
+
+ public String getScheme() {
+ return scheme;
+ }
+
+ public String getServerName() {
+ return server;
+ }
+
+ public int getServerPort() {
+ return port;
+ }
+
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+ public void removeAttribute(String name) {
+
+ }
+
+ public void setAttribute(String name, Object o) {
+
+ }
+
+ public void setCharacterEncoding(String env) {
+
+ }
+
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ return null;
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletResponse.java b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletResponse.java
new file mode 100644
index 0000000..5228110
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/sling/MockSlingHttpServletResponse.java
@@ -0,0 +1,196 @@
+/*
+ * 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.commons.testing.sling;
+
+import org.apache.sling.api.SlingHttpServletResponse;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+public class MockSlingHttpServletResponse implements SlingHttpServletResponse {
+
+ private StringBuffer output = new StringBuffer();
+ private String contentType;
+ private String encoding;
+ private int status = SC_OK;
+
+ public StringBuffer getOutput() {
+ return output;
+ }
+
+ public void addCookie(Cookie cookie) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".addCookie");
+ }
+
+ public boolean containsHeader(String s) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".containsHeader");
+ }
+
+ public String encodeURL(String s) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".encodeURL");
+ }
+
+ public String encodeRedirectURL(String s) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".encodeRedirectURL");
+ }
+
+ public String encodeUrl(String s) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".encodeUrl");
+ }
+
+ public String encodeRedirectUrl(String s) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".encodeRedirectUrl");
+ }
+
+ public void sendError(int i, String s) throws IOException {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".sendError");
+ }
+
+ public void sendError(int i) throws IOException {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".sendError");
+ }
+
+ public void sendRedirect(String s) throws IOException {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".sendRedirect");
+ }
+
+ public void setDateHeader(String s, long l) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setDateHeader");
+ }
+
+ public void addDateHeader(String s, long l) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".addDateHeader");
+ }
+
+ public void setHeader(String s, String s1) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setHeader");
+ }
+
+ public void addHeader(String s, String s1) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".addHeader");
+ }
+
+ public void setIntHeader(String s, int i) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setIntHeader");
+ }
+
+ public void addIntHeader(String s, int i) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".addIntHeader");
+ }
+
+ public void setStatus(int i) {
+ this.status = i;
+ }
+
+ public void setStatus(int i, String s) {
+ this.status = i;
+ }
+
+ public String getCharacterEncoding() {
+ return encoding;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".getOutputStream");
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ MockSlingHttpServletResponse.MockWriter writer = new MockWriter(output);
+ return new PrintWriter(writer);
+ }
+
+ public void setCharacterEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public void setContentLength(int i) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setContentLength");
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public void setBufferSize(int i) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setBufferSize");
+ }
+
+ public int getBufferSize() {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".getBufferSize");
+ }
+
+ public void flushBuffer() throws IOException {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".flushBuffer");
+ }
+
+ public void resetBuffer() {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".resetBuffer");
+ }
+
+ public boolean isCommitted() {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".isCommitted");
+ }
+
+ public void reset() {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".reset");
+ }
+
+ public void setLocale(Locale locale) {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".setLocale");
+ }
+
+ public Locale getLocale() {
+ throw new UnsupportedOperationException("Not implemented: " + getClass().getName() + ".getLocale");
+ }
+
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> adapterTypeClass) {
+ return null;
+ }
+
+ private class MockWriter extends Writer {
+ private StringBuffer buf;
+
+ public MockWriter(StringBuffer output) {
+ buf = output;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ buf.append(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ buf.setLength(0);
+ }
+
+ @Override
+ public void close() throws IOException {
+ buf = null;
+ }
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/util/JavascriptEngine.java b/testing/src/main/java/org/apache/sling/commons/testing/util/JavascriptEngine.java
new file mode 100644
index 0000000..f157017
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/util/JavascriptEngine.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.commons.testing.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScriptableObject;
+
+/** Simplistic Javascript engine using Rhino, meant
+ * for automated tests */
+public class JavascriptEngine {
+ /** Execute supplied code against supplied data,
+ * see JavascriptEngineTest for examples */
+ public String execute(String code, String jsonData) throws IOException {
+ final String jsCode = "data=" + jsonData + ";\n" + code;
+ final Context rhinoContext = Context.enter();
+ final ScriptableObject scope = rhinoContext.initStandardObjects();
+
+ // execute the script, out script variable maps to sw
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw, true);
+ ScriptableObject.putProperty(scope, "out", Context.javaToJS(pw, scope));
+ final int lineNumber = 1;
+ final Object securityDomain = null;
+ try {
+ rhinoContext.evaluateString(
+ scope,
+ jsCode,
+ getClass().getSimpleName(),
+ lineNumber,
+ securityDomain);
+ } catch(Exception e) {
+ final IOException ioe = new IOException("While executing [" + code + "]:" + e);
+ ioe.initCause(e);
+ throw ioe;
+ }
+
+ // check script output
+ pw.flush();
+ return sw.toString().trim();
+ }
+}
diff --git a/testing/src/main/java/org/apache/sling/commons/testing/util/TestStringUtil.java b/testing/src/main/java/org/apache/sling/commons/testing/util/TestStringUtil.java
new file mode 100644
index 0000000..f732d62
--- /dev/null
+++ b/testing/src/main/java/org/apache/sling/commons/testing/util/TestStringUtil.java
@@ -0,0 +1,40 @@
+/*
+ * 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.commons.testing.util;
+
+/** String utilities for testing */
+public class TestStringUtil {
+ static private final String NATIVE_LINE_SEP = System.getProperty("line.separator");
+
+ /** Replace \n with . in strings to make it easier to compare visually for testing */
+ public static String flatten(String str) {
+
+ // First replace native line-endings
+ if(str.indexOf(NATIVE_LINE_SEP) >= 0) {
+ str = str.replace(NATIVE_LINE_SEP, ".");
+ }
+
+ // Now find non-native line-endings, e.g. cygwin needs this
+ if(str.indexOf('\n') >= 0) {
+ str = str.replace('\n', '.');
+ }
+
+ return str;
+ }
+}
diff --git a/testing/src/main/resources/jackrabbit-test-config.xml b/testing/src/main/resources/jackrabbit-test-config.xml
new file mode 100644
index 0000000..7df6bfa
--- /dev/null
+++ b/testing/src/main/resources/jackrabbit-test-config.xml
@@ -0,0 +1,145 @@
+<?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 2.0//EN"
+ "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+
+<!-- Example Repository Configuration File
+ Used by
+ - org.apache.jackrabbit.core.config.RepositoryConfigTest.java
+ -
+-->
+<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>
+
+ <!--
+ data store configuration
+ -->
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+
+ <!--
+ security configuration
+ -->
+ <Security appName="Jackrabbit">
+ <!--
+ security manager:
+ class: FQN of class implementing the JackrabbitSecurityManager interface
+ -->
+ <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security">
+ <!--
+ workspace access:
+ class: FQN of class implementing the WorkspaceAccessManager interface
+ -->
+ <!-- <WorkspaceAccessManager class="..."/> -->
+ <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+ </SecurityManager>
+
+ <!--
+ access manager:
+ class: FQN of class implementing the AccessManager interface
+ -->
+ <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+ <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+ </AccessManager>
+
+ <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+ <!--
+ anonymous user name ('anonymous' is the default value)
+ -->
+ <param name="anonymousId" value="anonymous"/>
+ <!--
+ administrator user id (default value if param is missing is 'admin')
+ -->
+ <param name="adminId" value="admin"/>
+ </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="${wsp.name}">
+ <!--
+ 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.pool.DerbyPersistenceManager">
+ <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+ <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+ </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"/>
+ <param name="supportHighlighting" value="true"/>
+ </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.pool.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"/>
+ <param name="supportHighlighting" value="true"/>
+ </SearchIndex>
+</Repository>
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryProviderTest.java b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryProviderTest.java
new file mode 100644
index 0000000..e0cd15d
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryProviderTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.commons.testing.jcr;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.junit.Before;
+import org.junit.Test;
+
+/** JUnit 4 style RepositoryProvider test */
+public class RepositoryProviderTest {
+ private SlingRepository repo;
+
+ @Before
+ public void getRepo() throws Exception {
+ repo = RepositoryProvider.instance().getRepository();
+ }
+
+ @Test
+ public void testRepository() throws Exception {
+ assertNotNull("Expecting SlingRepository to be setup", repo);
+ }
+
+ @Test
+ public void testRootNode() throws Exception {
+ final Session s = repo.loginAdministrative(null);
+ try {
+ assertNotNull("Expecting a non-null Session", s);
+ } finally {
+ s.logout();
+ }
+ }
+}
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryTestBaseTest.java b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryTestBaseTest.java
new file mode 100644
index 0000000..8881009
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryTestBaseTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.commons.testing.jcr;
+
+import javax.jcr.Node;
+
+/** Test the RepositoryTestBase */
+public class RepositoryTestBaseTest extends RepositoryTestBase {
+
+ public void testTestNode() throws Exception {
+ final Node n = getTestRootNode();
+ assertNotNull("Expecting to get a test Node", n);
+ }
+}
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryUtilTest.java b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryUtilTest.java
new file mode 100644
index 0000000..81c392d
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/jcr/RepositoryUtilTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.commons.testing.jcr;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.jcr.Session;
+
+/**
+ * This is a simple test for the repository util. We start
+ * the repository, try to login as admin and anonymous and
+ * then stop the repository.
+ */
+public class RepositoryUtilTest {
+
+ @org.junit.Test public void testRepository() throws Exception {
+ // start the repository
+ RepositoryUtil.startRepository();
+
+ // get admin session
+ final Session adminSession = RepositoryUtil.getRepository().loginAdministrative(null);
+ assertNotNull(adminSession);
+ adminSession.logout();
+
+ // get anonymous session
+ final Session anonSession = RepositoryUtil.getRepository().login();
+ assertNotNull(anonSession);
+ anonSession.logout();
+
+ // stop the repository
+ RepositoryUtil.stopRepository();
+ }
+}
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/junit/RetryRuleTest.java b/testing/src/test/java/org/apache/sling/commons/testing/junit/RetryRuleTest.java
new file mode 100644
index 0000000..ea03904
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/junit/RetryRuleTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.commons.testing.junit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class RetryRuleTest {
+ private int setupCounter;
+ private int callCount = 0;
+ private long callTime = -1;
+
+ @Rule
+ public final RetryRule retryRule = new RetryRule();
+
+ @Before
+ public void setup() {
+ setupCounter = 0;
+ }
+
+ @Retry
+ @Test
+ public void testDefaultParameters() {
+ callCount++;
+ setupCounter++;
+
+ if(callTime > 0) {
+ final long delta = System.currentTimeMillis();
+ assertTrue("Expecting at least 500 msec between calls", delta >= 500);
+ }
+ callTime = System.currentTimeMillis();
+
+ assertTrue("Expecting to be called several times before passing", callCount > 3);
+ assertEquals("Expecting setup() to be called before every retry", 1, setupCounter);
+
+ // Once we pass, reset counters for other tests
+ callTime = -1;
+ callCount = 0;
+ }
+
+ @Retry
+ @Test
+ public void testRetryOnException() throws Exception {
+ callCount++;
+ setupCounter++;
+
+ if(callCount <= 3) {
+ throw new Exception("Expecting to be called several times before passing");
+ }
+
+ assertEquals("Expecting setup() to be called before every retry", 1, setupCounter);
+
+ // Once we pass, reset counters for other tests
+ callTime = -1;
+ callCount = 0;
+ }
+
+ @Retry(timeoutMsec=500, intervalMsec=1)
+ @Test
+ public void testCustomParameters() {
+ callCount++;
+ setupCounter++;
+
+ assertTrue("Expecting to be called many times before passing", callCount > 100);
+ assertEquals("Expecting setup() to be called before every retry", 1, setupCounter);
+
+ // Once we pass, reset callTime for other tests
+ callTime = -1;
+ callCount = 0;
+ }
+
+ @Test
+ public void testDefaultDefaultTimings() {
+ final RetryRule r = new RetryRule();
+ assertEquals("Expecting default default timeout", RetryRule.DEFAULT_DEFAULT_TIMEOUT_MSEC, r.getTimeout(-1));
+ assertEquals("Expecting default default interval", RetryRule.DEFAULT_DEFAULT_INTERVAL_MSEC, r.getInterval(-1));
+ }
+
+ @Test
+ public void testSpecifiedDefaultTimings() {
+ final RetryRule r = new RetryRule(12, 42);
+ assertEquals("Expecting specified default timeout", 12, r.getTimeout(-1));
+ assertEquals("Expecting specified default interval", 42, r.getInterval(-1));
+ }
+
+ @Test
+ public void testRuleTimings() {
+ final RetryRule r = new RetryRule(12, 42);
+ assertEquals("Expecting timeout from Rule", 1012, r.getTimeout(1012));
+ assertEquals("Expecting interval from Rule", 1024, r.getInterval(1024));
+ }
+}
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/sling/MockResourceTest.java b/testing/src/test/java/org/apache/sling/commons/testing/sling/MockResourceTest.java
new file mode 100644
index 0000000..7661bae
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/sling/MockResourceTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.testing.sling;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Test;
+
+public class MockResourceTest {
+ public static final String PATH = "/foo/bar";
+ public static final String RT = "someResourceType";
+
+ @Test
+ public void basicTest() throws Exception {
+ final MockResource r = new MockResource(null, PATH, RT);
+ r.addProperty("1", Integer.MAX_VALUE);
+ r.addProperty("2", "two");
+
+ assertTrue(r instanceof Resource);
+ assertEquals(PATH, r.getPath());
+ assertEquals(RT, r.getResourceType());
+ assertNull(r.getResourceSuperType());
+
+ final ValueMap m = r.adaptTo(ValueMap.class);
+ assertEquals(Integer.MAX_VALUE, m.get("1"));
+ assertEquals("two", m.get("2"));
+
+ // changes to r do not affect m
+ assertNull(m.get("third"));
+ r.getProperties().put("third", "trois");
+ assertNull(m.get("third"));
+
+ // but we get the new value after adapting again
+ assertEquals("trois", r.adaptTo(ValueMap.class).get("third"));
+ }
+
+ @Test
+ public void testNoProperties() throws Exception {
+ final MockResource r = new MockResource(null, PATH, RT);
+ assertNotNull(r.adaptTo(ValueMap.class));
+ }
+}
\ No newline at end of file
diff --git a/testing/src/test/java/org/apache/sling/commons/testing/util/JavascriptEngineTest.java b/testing/src/test/java/org/apache/sling/commons/testing/util/JavascriptEngineTest.java
new file mode 100644
index 0000000..7f4d78a
--- /dev/null
+++ b/testing/src/test/java/org/apache/sling/commons/testing/util/JavascriptEngineTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.commons.testing.util;
+
+import java.io.IOException;
+
+import org.apache.sling.commons.testing.util.JavascriptEngine;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class JavascriptEngineTest {
+ private final JavascriptEngine engine = new JavascriptEngine();
+
+ @Test
+ public void testSimpleScripts() throws IOException {
+
+ // Each triplet of data is: json data, code, expected output
+ final String [] data = {
+ "{}", "out.print('hello')", "hello",
+ "{ i:26, j:'a' }", "out.print(data.i + data.j)", "26a",
+ "{ i:'ab', j:'cd' }", "out.print(data.i) ; out.print('+') ; out.print(data.j)", "ab+cd"
+ };
+
+ for(int i=0; i < data.length; i+= 3) {
+ final String json = data[i];
+ final String code = data[i+1];
+ final String expected = data[i+2];
+ final String actual = engine.execute(code, json);
+ assertEquals("At index " + i, expected, actual);
+ }
+ }
+}