You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/20 14:44:19 UTC

[sling-org-apache-sling-testing-clients] branch master created (now 102ac8a)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git.


      at 102ac8a  SLING-7167 Adjust READMEs

This branch includes the following new commits:

     new b29fdd6  SLING-5703 - new http/clients module, extracted and enhanced from testing/tools. Contributed by Andrei Dulvac, thanks!
     new d742769  SLING-5725 - Remove o.a.s.testing.tools dependency in o.a.s.testing.clients and merge OSGi console clients - contribued by Andrei Dulvac, thanks!
     new 79179d9  trivial - reduced wait time in test
     new 3c75f9f  SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code
     new 5f3c3cf  SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code  * Added TimeoutsProvider
     new 0d733dd  SLING-5793 Add client that can leverage hapi client in testing http clients
     new 88f90b0  @trivial Added missing package version
     new 5f0e667  @trivial Added http status code to ClientException
     new 7303bc3  Rollback release of org.apache.sling.hapi.client
     new fd6fed2  Fix compilation errors
     new 9e4c309  @releng updated dependency in org.apache.sling.testing.clients to hapi client 1.0.0
     new 114c5fa  @releng changed package versions in org.apache.sling.testing.clients
     new 16afc27  @releng changed package versions in org.apache.sling.testing.clients
     new 200bc43  [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.0
     new 295db9a  [maven-release-plugin] prepare for next development iteration
     new ce58fd7  Fix broken build - this module relies on an hapi snapshot from contrib, that's not good but at least the build should pass now (I have deployed hapi snapshots)
     new 1ded2db  SLING-6405 - Make testing.clients.interceptors.TestDescriptionInterceptor in line with TestLogServlet from junit core
     new 598c44e  SLING-6431 and some cosmetic changes
     new 48d3919  [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.1
     new d8581d1  [maven-release-plugin] prepare for next development iteration
     new 2ba5632  use Sling Parent 30
     new 7915f4f  use OSGi annotations and remove unused Maven SCR Plugin
     new bd20f6a  SLING-6853 Improve polling capabilities in o.a.s.testing.clients thanks @volteanu for the contribution!
     new 8de4abd  SLING-6853 Fixed incomplete patch.
     new dd1dfb8  SLING-6905: Remove commons.json from testing http clients by using already used jackson library. Patch provided by Valentin Olteanu. This closes #235.
     new e58b2e9  [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.0
     new f57f13c  [maven-release-plugin] prepare for next development iteration
     new bddb9f8  SLING-6954 - Add client support for the org.apache.sling.testing.email bundle
     new 414ab85  [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.2
     new 4be7f2b  [maven-release-plugin] prepare for next development iteration
     new e249221  SLING-6964 - SlingEmailClient does not allow accessing email headers
     new d0c4868  [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.4
     new faa7305  [maven-release-plugin] prepare for next development iteration
     new 1f4e21c  SLING-7029 - Extension - adding method to stop a bundle
     new be0228b  SLING-7029 - Extension - adding method to stop a bundle
     new 7ef662d  SLING-7116 [http.testing.clients] o.a.s.testing.clients.osgi.ComponentsInfo.findBy() does not honor the value parameter
     new 102ac8a  SLING-7167 Adjust READMEs

The 37 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-testing-clients] 20/37: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit d8581d143b642108b9e585b21cbe3501bdfc919d
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Fri Jan 6 10:01:21 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1777560 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 72731ed..17392ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.0.1</version>
+    <version>1.0.2-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.1</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.1</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.0.1</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 10/37: Fix compilation errors

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit fd6fed289dd64733a4ac729b47fb040a0509a345
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Aug 10 05:22:15 2016 +0000

    Fix compilation errors
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1755669 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                            | 7 +------
 .../org/apache/sling/testing/clients/html/MicrodataClient.java     | 6 +++---
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index 3c80c86..471bbcf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,11 +79,6 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>
-       <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.scr.annotations</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-exec</artifactId>
@@ -156,7 +151,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.hapi.client</artifactId>
-            <version>1.0.0-SNAPSHOT</version>
+            <version>1.0.1-SNAPSHOT</version>
         </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java b/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java
index 810a28c..ab90d90 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java
@@ -18,18 +18,18 @@
  ******************************************************************************/
 package org.apache.sling.testing.clients.html;
 
+import java.net.URI;
+
 import org.apache.http.HttpEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.sling.hapi.client.HtmlClient;
-import org.apache.sling.hapi.client.microdata.MicrodataDocument;
+import org.apache.sling.hapi.client.impl.microdata.MicrodataDocument;
 import org.apache.sling.testing.clients.ClientException;
 import org.apache.sling.testing.clients.SlingClient;
 import org.apache.sling.testing.clients.SlingClientConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.URI;
-
 public class MicrodataClient extends SlingClient implements HtmlClient {
     protected static final Logger LOG = LoggerFactory.getLogger(MicrodataClient.class);
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 36/37: SLING-7116 [http.testing.clients] o.a.s.testing.clients.osgi.ComponentsInfo.findBy() does not honor the value parameter

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 7ef662d711709c26faacf4fe5c1aa5dc10bd9e26
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Fri Sep 8 09:07:34 2017 +0000

    SLING-7116 [http.testing.clients] o.a.s.testing.clients.osgi.ComponentsInfo.findBy() does not honor the value parameter
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1807694 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sling/testing/clients/osgi/BundlesInfo.java     | 10 ++++------
 .../org/apache/sling/testing/clients/osgi/ComponentsInfo.java  |  6 +++---
 2 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
index 7ada2da..b99fbf5 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
@@ -129,12 +129,10 @@ public class BundlesInfo {
         Iterator<JsonNode> nodes = root.get("data").getElements();
         while(nodes.hasNext()) {
             JsonNode node = nodes.next();
-            if(node.get(key) != null) {
-                if(node.get(key).isValueNode()) {
-                	String valueNode=node.get(key).getTextValue();
-                	if (valueNode.equals(value)){
-                		return node;
-                	}
+            if ((null != node.get(key)) && (node.get(key).isValueNode())) {
+                final String valueNode = node.get(key).getTextValue();
+                if (valueNode.equals(value)) {
+                    return node;
                 }
             }
         }
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
index 174544e..49b7b14 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
@@ -83,13 +83,13 @@ public class ComponentsInfo {
         Iterator<JsonNode> nodes = root.get("data").getElements();
         while(nodes.hasNext()) {
             JsonNode node = nodes.next();
-            if(node.get(key) != null) {
-                if(node.get(key).isValueNode()) {
+            if ((null != node.get(key)) && (node.get(key).isValueNode())) {
+                final String valueNode = node.get(key).getTextValue();
+                if (valueNode.equals(value)) {
                     return node;
                 }
             }
         }
         return null;
     }
-
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 16/37: Fix broken build - this module relies on an hapi snapshot from contrib, that's not good but at least the build should pass now (I have deployed hapi snapshots)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit ce58fd7fb3b99aa99d20070def86575bca8ee650
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri Oct 14 09:15:56 2016 +0000

    Fix broken build - this module relies on an hapi snapshot from contrib, that's not good but at least the build should pass now (I have deployed hapi snapshots)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1764841 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 1d09323..1267c96 100644
--- a/pom.xml
+++ b/pom.xml
@@ -151,7 +151,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.hapi.client</artifactId>
-            <version>1.0.0</version>
+            <version>1.0.1-SNAPSHOT</version>
         </dependency>
     </dependencies>
 </project>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 19/37: [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.1

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 48d3919000d3a210524c5a7cfc62ad5cae1a5b16
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Fri Jan 6 10:01:05 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.1
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1777558 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 4a326bd..72731ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.0.1-SNAPSHOT</version>
+    <version>1.0.1</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.1</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.1</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.0.1</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 02/37: SLING-5725 - Remove o.a.s.testing.tools dependency in o.a.s.testing.clients and merge OSGi console clients - contribued by Andrei Dulvac, thanks!

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit d742769421ec3205a6bd76273340a99e9256b670
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri May 13 14:02:03 2016 +0000

    SLING-5725 - Remove o.a.s.testing.tools dependency in o.a.s.testing.clients and merge OSGi console clients - contribued by Andrei Dulvac, thanks!
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1743676 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  24 +-
 .../testing/clients/osgi/BundlesInstaller.java     | 243 +++++++++--------
 .../testing/clients/osgi/OsgiConsoleClient.java    | 293 ++++++++++++++++++++-
 .../testing/clients/osgi/WebconsoleClient.java     | 190 -------------
 .../testing/clients/util/poller/PathPoller.java    |  64 +++++
 5 files changed, 505 insertions(+), 309 deletions(-)

diff --git a/pom.xml b/pom.xml
index f0da527..b67851d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,17 +69,6 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.testing.tools</artifactId>
-            <version>1.0.12</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.apache.httpcomponents</groupId>
-                    <artifactId>httpcore</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>
@@ -144,5 +133,18 @@
             <artifactId>org.apache.sling.xss</artifactId>
             <version>1.0.4</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.16</version>
+        </dependency>
+
+        <!-- For tests -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
index 1c737a4..e99c454 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
@@ -16,171 +16,202 @@
  */
 package org.apache.sling.testing.clients.osgi;
 
-import org.osgi.framework.Constants;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.util.poller.AbstractPoller;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.jar.JarInputStream;
-import java.util.jar.Manifest;
 
 
-/** Utility that installs and starts additional bundles for testing */ 
+/**
+ * Utility for installing and starting additional bundles for testing
+ */
 public class BundlesInstaller {
     private final Logger log = LoggerFactory.getLogger(getClass());
-    private final WebconsoleClient webconsoleClient;
+    private final OsgiConsoleClient osgiConsoleClient;
     public static final String ACTIVE_STATE = "active";
-    
-    public BundlesInstaller(WebconsoleClient wcc) {
-        webconsoleClient = wcc;
+
+    public BundlesInstaller(OsgiConsoleClient cc) {
+        osgiConsoleClient = cc;
     }
-   
-    public boolean isInstalled(File bundleFile) throws Exception {
-        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
-        try{
-            log.debug("Checking if installed: "+bundleSymbolicName);
-            webconsoleClient.checkBundleInstalled(bundleSymbolicName, 1);
-            // if this succeeds, then there's no need to install again
-            log.debug("Already installed: "+bundleSymbolicName);
+
+    /**
+     * Checks if a bundle is installed or not. Does not retry.
+     * @param bundleFile
+     * @return
+     * @throws ClientException
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public boolean isInstalled(File bundleFile) throws ClientException, InterruptedException, IOException {
+        final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
+        log.debug("Checking if installed: " + bundleSymbolicName);
+        boolean installed = osgiConsoleClient.checkBundleInstalled(bundleSymbolicName, 1000, 1);
+        // if this succeeds, then there's no need to install again
+        if (installed) {
+            log.debug("Already installed: " + bundleSymbolicName);
             return true;
-        } catch(AssertionError e) {
-            log.debug("Not yet installed: "+bundleSymbolicName);
+        } else {
+            log.debug("Not yet installed: " + bundleSymbolicName);
             return false;
         }
-
     }
-    
-    /** Check if the installed version matches the one of the bundle (file) **/
-    public boolean isInstalledWithSameVersion(File bundleFile) throws Exception {
-        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
-        final String versionOnServer = webconsoleClient.getBundleVersion(bundleSymbolicName);
-        final String versionInBundle = getBundleVersion(bundleFile);
+
+    /**
+     * Check if the installed version matches the one of the bundle (file)
+     * @param bundleFile
+     * @return
+     * @throws Exception
+     */
+    public boolean isInstalledWithSameVersion(File bundleFile) throws ClientException, IOException {
+        final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
+        final String versionOnServer = osgiConsoleClient.getBundleVersion(bundleSymbolicName);
+        final String versionInBundle = OsgiConsoleClient.getBundleVersionFromFile(bundleFile);
         if (versionOnServer.equals(versionInBundle)) {
             return true;
         } else {
-            log.info("Bundle installed doesn't match: "+bundleSymbolicName+
-                    ", versionOnServer="+versionOnServer+", versionInBundle="+versionInBundle);
+            log.warn("Installed bundle doesn't match: {}, versionOnServer={}, versionInBundle={}",
+                    bundleSymbolicName, versionOnServer, versionInBundle);
             return false;
         }
     }
-    
-    /** Install a list of bundles supplied as Files */
-    public void installBundles(List<File> toInstall, boolean startBundles) throws Exception {
+
+    /**
+     * Install a list of bundles supplied as Files
+     * @param toInstall
+     * @param startBundles
+     * @throws Exception
+     */
+    public void installBundles(List<File> toInstall, boolean startBundles) throws ClientException, IOException, InterruptedException {
         for(File f : toInstall) {
-            final String bundleSymbolicName = getBundleSymbolicName(f);
+            final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
                 if (f.getName().contains("SNAPSHOT")) {
                     log.info("Reinstalling (due to SNAPSHOT version): {}", bundleSymbolicName);
-                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                    osgiConsoleClient.uninstallBundle(bundleSymbolicName);
                 } else if (!isInstalledWithSameVersion(f)) {
                     log.info("Reinstalling (due to version mismatch): {}", bundleSymbolicName);
-                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                    osgiConsoleClient.uninstallBundle(bundleSymbolicName);
                 } else {
                     log.info("Not reinstalling: {}", bundleSymbolicName);
                     continue;
                 }
             }
-            webconsoleClient.installBundle(f, startBundles);
+            osgiConsoleClient.installBundle(f, startBundles);
             log.info("Installed: {}", bundleSymbolicName);
         }
-        
+
         // ensure that bundles are re-wired esp. if an existing bundle was updated
-        webconsoleClient.refreshPackages();
+        osgiConsoleClient.refreshPackages();
 
         log.info("{} additional bundles installed", toInstall.size());
     }
-    
-    /** Uninstall a list of bundles supplied as Files */
-    public void uninstallBundles(List<File> toUninstall) throws Exception {
+
+    /**
+     * Uninstall a list of bundles supplied as Files
+     * @param toUninstall
+     * @throws ClientException
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void uninstallBundles(List<File> toUninstall) throws ClientException, IOException, InterruptedException {
         for(File f : toUninstall) {
-            final String bundleSymbolicName = getBundleSymbolicName(f);
+            final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
                 log.info("Uninstalling bundle: {}", bundleSymbolicName);
-                webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                osgiConsoleClient.uninstallBundle(bundleSymbolicName);
             } else {
                 log.info("Could not uninstall: {} as it never was installed", bundleSymbolicName);
             }
         }
-        
+
         // ensure that bundles are re-wired esp. if an existing bundle was updated
-        webconsoleClient.refreshPackages();
+        osgiConsoleClient.refreshPackages();
 
         log.info("{} additional bundles uninstalled", toUninstall.size());
     }
-    
-    /** Wait for all bundles specified in symbolicNames list to be installed in the
-     *  remote web console.
+
+
+    /**
+     * Wait for all bundles specified in symbolicNames list to be installed in the OSGi web console.
+     * @param symbolicNames the list of names for the bundles
+     * @param timeoutSeconds how many seconds to wait
+     * @return
+     * @throws Exception
      */
-    public void waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws Exception {
-        log.info("Checking that bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
-        for(String symbolicName : symbolicNames) {
-            webconsoleClient.checkBundleInstalled(symbolicName, timeoutSeconds);
+    public boolean waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
+        log.info("Checking that the following bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
+        for (String symbolicName : symbolicNames) {
+            boolean started = osgiConsoleClient.checkBundleInstalled(symbolicName, 500, 2 * timeoutSeconds);
+            if (!started) return false;
         }
+        return true;
     }
-    
-    public void startAllBundles(List<String> symbolicNames, int timeoutSeconds) throws Exception {
+
+    /**
+     * Start all the bundles in a {{List}}
+     * @param symbolicNames the list of bundles to start
+     * @param timeoutSeconds number of seconds until it times out
+     * @throws ClientException
+     * @throws InterruptedException
+     */
+    public void startAllBundles(final List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
         log.info("Starting bundles (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
-        
-        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000L;
-        final List<String> toStart = new LinkedList<String>();
-        while(System.currentTimeMillis() < timeout) {
-            toStart.clear();
-            for(String name : symbolicNames) {
-                final String state = webconsoleClient.getBundleState(name);
-                if(!state.equalsIgnoreCase(ACTIVE_STATE)) {
-                    toStart.add(name);
-                    break;
-                }
+        class StartAllBundlesPoller extends AbstractPoller {
+            private ClientException exception;
+            public StartAllBundlesPoller(List<String> symbolicNames, long waitInterval, long waitCount) {
+                super(waitInterval, waitCount);
             }
-            
-            if(toStart.isEmpty()) {
-                log.info("Ok - all bundles are in the {} state", ACTIVE_STATE);
-                break;
+
+            @Override
+            public boolean call() {
+                for (String bundle : symbolicNames) {
+                    final String state;
+                    try {
+                        state = osgiConsoleClient.getBundleState(bundle);
+                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                            osgiConsoleClient.startBundle(bundle);
+                        }
+                    } catch (ClientException e) {
+                        this.exception = e;
+                        return false;
+                    }
+                }
+                return true;
             }
-            
-            for(String name : toStart) {
-                webconsoleClient.startBundle(name);
+
+            @Override
+            public boolean condition() {
+                for (String bundle : symbolicNames) {
+                    final String state;
+                    try {
+                        state = osgiConsoleClient.getBundleState(bundle);
+                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                            return false;
+                        }
+                    } catch (ClientException e) {
+                        this.exception = e;
+                        return false;
+                    }
+                }
+                return true;
             }
-            
-            Thread.sleep(500L);
-        }
-        
-        if(!toStart.isEmpty()) {
-            throw new Exception("Some bundles did not start: " + toStart);
-        }
-    }
-    
-    public String getBundleSymbolicName(File bundleFile) throws IOException {
-        String name = null;
-        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
-        try {
-            final Manifest m = jis.getManifest();
-            if (m == null) {
-                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+
+            public ClientException getException() {
+                return exception;
             }
-            name = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-        } finally {
-            jis.close();
         }
-        return name;
-    }
-    
-    public String getBundleVersion(File bundleFile) throws IOException {
-        String version = null;
-        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
-        try {
-            final Manifest m = jis.getManifest();
-            if(m == null) {
-                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
-            }
-            version = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-        } finally {
-            jis.close();
+        StartAllBundlesPoller poller = new StartAllBundlesPoller(symbolicNames, 1000, timeoutSeconds);
+        if (!poller.callUntilCondition()) {
+            throw new ClientException("Some bundles did not start or timed out", poller.getException());
         }
-        return version;
+
     }
+
+
+
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 9c5a928..08cd742 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -18,7 +18,11 @@
 package org.apache.sling.testing.clients.osgi;
 
 import org.apache.http.Header;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.testing.clients.ClientException;
 import org.apache.sling.testing.clients.SlingHttpResponse;
 import org.apache.sling.testing.clients.util.JsonUtils;
@@ -27,18 +31,27 @@ import org.apache.sling.testing.clients.SlingClient;
 import org.apache.sling.testing.clients.SlingClientConfig;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
+import org.apache.sling.testing.clients.util.poller.PathPoller;
 import org.codehaus.jackson.JsonNode;
+import org.osgi.framework.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.URI;
 import java.util.*;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
 
 import static org.apache.http.HttpStatus.SC_MOVED_TEMPORARILY;
 import static org.apache.http.HttpStatus.SC_OK;
 
 /**
  * A client that wraps the Felix OSGi Web Console REST API calls.
+ * @see <a href=http://felix.apache.org/documentation/subprojects/apache-felix-web-console/web-console-restful-api.html>
+ *     Web Console RESTful API</a>
  */
 public class OsgiConsoleClient extends SlingClient {
 
@@ -63,6 +76,12 @@ public class OsgiConsoleClient extends SlingClient {
      */
     private final String URL_COMPONENTS = CONSOLE_ROOT_URL + "/components";
 
+
+    public static final String JSON_KEY_ID = "id";
+    public static final String JSON_KEY_VERSION = "version";
+    public static final String JSON_KEY_DATA = "data";
+    public static final String JSON_KEY_STATE = "state";
+
     /**
      * Default constructor. Simply calls {@link SlingClient#SlingClient(URI, String, String)}
      *
@@ -141,6 +160,10 @@ public class OsgiConsoleClient extends SlingClient {
         return new ComponentInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
     }
 
+    //
+    // OSGi configurations
+    //
+
     /**
      * Returns a map of all properties set for the config referenced by the PID, where the map keys
      * are the property names.
@@ -276,16 +299,282 @@ public class OsgiConsoleClient extends SlingClient {
      *
      * @param pid pid
      * @param expectedStatus expected response status
+     * @return the sling response
      * @throws ClientException if the response status does not match any of the expectedStatus
      */
-    public void deleteConfiguration(String pid, int... expectedStatus) throws ClientException {
+    public SlingHttpResponse deleteConfiguration(String pid, int... expectedStatus) throws ClientException {
         FormEntityBuilder builder = FormEntityBuilder.create();
         builder.addParameter("apply", "1");
         builder.addParameter("delete", "1");
         // make the request
         SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + pid, builder.build());
-        // check the returned status         
+        // check the returned status
         HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(200, expectedStatus));
+        return resp;
+    }
+
+    //
+    // Bundles
+    //
+
+    /**
+     * Uninstall a bundle
+     * @param symbolicName
+     * @return the sling response
+     * @throws ClientException
+     */
+    public SlingHttpResponse uninstallBundle(String symbolicName) throws ClientException {
+        final long bundleId = getBundleId(symbolicName);
+        LOG.info("Uninstalling bundle {} with bundleId {}", symbolicName, bundleId);
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("action", "uninstall");
+        return this.doPost(getBundlePath(symbolicName), builder.build(), 200);
+    }
+
+    /**
+     * Install a bundle using the Felix webconsole HTTP interface
+     * @param f the bundle file
+     * @param startBundle whether to start the bundle or not
+     * @return the sling response
+     * @throws ClientException
+     */
+    public SlingHttpResponse installBundle(File f, boolean startBundle) throws ClientException {
+        return installBundle(f, startBundle, 0);
+    }
+
+    /**
+     * Install a bundle using the Felix webconsole HTTP interface, with a specific start level
+     * @param f
+     * @param startBundle
+     * @param startLevel
+     * @return the sling response
+     * @throws ClientException
+     */
+    public SlingHttpResponse installBundle(File f, boolean startBundle, int startLevel) throws ClientException {
+        // Setup request for Felix Webconsole bundle install
+        MultipartEntityBuilder builder = MultipartEntityBuilder.create()
+                .addTextBody("action", "install")
+                .addBinaryBody("bundlefile", f);
+        if (startBundle) {
+            builder.addTextBody("bundlestart", "true");
+        }
+        if (startLevel > 0) {
+            builder.addTextBody("bundlestartlevel", String.valueOf(startLevel));
+            LOG.info("Installing bundle {} at start level {}", f.getName(), startLevel);
+        } else {
+            LOG.info("Installing bundle {} at default start level", f.getName());
+        }
+
+        return this.doPost(URL_BUNDLES, builder.build(), 302);
+
+    }
+
+    /**
+     * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * @param f the bundle file
+     * @param startBundle whether to start the bundle or not
+     * @param startLevel the start level of the bundle. negative values mean default start level
+     * @param waitTime how long to wait between retries of checking the bundle
+     * @param retries how many times to check for the bundle to be installed, until giving up
+     * @return true if the bundle was successfully installed, false otherwise
+     * @throws ClientException
+     */
+    public boolean installBundleWithRetry(File f, boolean startBundle, int startLevel, int waitTime, int retries)
+            throws ClientException, InterruptedException {
+        installBundle(f, startBundle, startLevel);
+        try {
+            return this.checkBundleInstalled(OsgiConsoleClient.getBundleSymbolicName(f), waitTime, retries);
+        } catch (IOException e) {
+            throw new ClientException("Cannot get bundle symbolic name", e);
+        }
+    }
+
+    /**
+     * Check that specified bundle is installed and retries every {{waitTime}} milliseconds, until the
+     * bundle is installed or the number of retries was reached
+     * @param symbolicName the name of the bundle
+     * @param waitTime How many milliseconds to wait between retries
+     * @param retries the number of retries
+     * @return true if the bundle was installed until the retries stop, false otherwise
+     * @throws InterruptedException
+     */
+    public boolean checkBundleInstalled(String symbolicName, int waitTime, int retries) throws InterruptedException {
+        final String path = getBundlePath(symbolicName, ".json");
+        return new PathPoller(this, path, waitTime, retries).callAndWait();
+    }
+
+    /**
+     * Get the id of the bundle
+     * @param symbolicName
+     * @return
+     * @throws Exception
+     */
+    public long getBundleId(String symbolicName) throws ClientException {
+        final JSONObject bundle = getBundleData(symbolicName);
+        try {
+            return bundle.getLong(JSON_KEY_ID);
+        } catch (JSONException e) {
+            throw new ClientException("Cannot get id from json", e);
+        }
+    }
+
+    /**
+     * Get the version of the bundle
+     * @param symbolicName
+     * @return
+     * @throws ClientException
+     */
+    public String getBundleVersion(String symbolicName) throws ClientException {
+        final JSONObject bundle = getBundleData(symbolicName);
+        try {
+            return bundle.getString(JSON_KEY_VERSION);
+        } catch (JSONException e) {
+            throw new ClientException("Cannot get version from json", e);
+        }
+    }
+
+    /**
+     * Get the state of the bundle
+     * @param symbolicName
+     * @return
+     * @throws Exception
+     */
+    public String getBundleState(String symbolicName) throws ClientException {
+        final JSONObject bundle = getBundleData(symbolicName);
+        try {
+            return bundle.getString(JSON_KEY_STATE);
+        } catch (JSONException e) {
+            throw new ClientException("Cannot get state from json", e);
+        }
+    }
+
+    /**
+     * Starts a bundle
+     * @param symbolicName the name of the bundle
+     * @throws ClientException
+     */
+    public void startBundle(String symbolicName) throws ClientException {
+        // To start the bundle we POST action=start to its URL
+        final String path = getBundlePath(symbolicName);
+        LOG.info("Starting bundle {} via {}", symbolicName, path);
+        this.doPost(path, FormEntityBuilder.create().addParameter("action", "start").build(), SC_OK);
+    }
+
+    /**
+     * Starts a bundle and waits for it to be started
+     * @param symbolicName the name of the bundle
+     * @param waitTime How many milliseconds to wait between retries
+     * @param retries the number of retries
+     * @throws ClientException, InterruptedException
+     */
+    public void startBundlewithWait(String symbolicName, int waitTime, int retries)
+            throws ClientException, InterruptedException {
+        // start a bundle
+        startBundle(symbolicName);
+        // wait for it to be in the started state
+        checkBundleInstalled(symbolicName, waitTime, retries);
+    }
+
+    /**
+     * Calls PackageAdmin.refreshPackages to force re-wiring of all the bundles.
+     * @throws ClientException
+     */
+    public void refreshPackages() throws ClientException {
+        LOG.info("Refreshing packages.");
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("action", "refreshPackages");
+        this.doPost(URL_BUNDLES, builder.build(), 200);
+    }
+
+
+    //
+    // private methods
+    //
+
+    private String getBundlePath(String symbolicName, String extension) {
+        return getBundlePath(symbolicName) + extension;
+    }
+
+    private String getBundlePath(String symbolicName) {
+        return URL_BUNDLES + "/" + symbolicName;
+    }
+
+    private JSONObject getBundleData(String symbolicName) throws ClientException {
+        // This returns a data structure like
+        // {"status":"Bundle information: 173 bundles in total - all 173 bundles active.","s":[173,171,2,0,0],"data":
+        //  [
+        //      {"id":0,"name":"System Bundle","fragment":false,"stateRaw":32,"state":"Active","version":"3.0.7","symbolicName":"org.apache.felix.framework","category":""},
+        //  ]}
+        final String path = getBundlePath(symbolicName, ".json");
+        final String content = this.doGet(path, SC_OK).getContent();
+
+        try {
+            final JSONObject root = new JSONObject(content);
+
+            if (!root.has(JSON_KEY_DATA)) {
+                throw new ClientException(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
+            }
+
+            final JSONArray data = root.getJSONArray(JSON_KEY_DATA);
+            if (data.length() < 1) {
+                throw new ClientException(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
+            }
+
+            final JSONObject bundle = data.getJSONObject(0);
+            if (!bundle.has(JSON_KEY_STATE)) {
+                throw new ClientException(path + ".data[0].state missing, JSON content=" + content);
+            }
+
+            return bundle;
+        } catch (JSONException e) {
+            throw new ClientException("Cannot get json", e);
+        }
+    }
+
+    //
+    // static methods
+    //
+
+    /**
+     * Get the symbolic name from a bundle file
+     * @param bundleFile
+     * @return
+     * @throws IOException
+     */
+    public static String getBundleSymbolicName(File bundleFile) throws IOException {
+        String name = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if (m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            name = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+        } finally {
+            jis.close();
+        }
+        return name;
+    }
+
+    /**
+     * Get the version form a bundle file
+     * @param bundleFile
+     * @return
+     * @throws IOException
+     */
+    public static String getBundleVersionFromFile(File bundleFile) throws IOException {
+        String version = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if(m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            version = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+        } finally {
+            jis.close();
+        }
+        return version;
     }
 
 
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
deleted file mode 100644
index bbda9bc..0000000
--- a/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
+++ /dev/null
@@ -1,190 +0,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.
- */
-package org.apache.sling.testing.clients.osgi;
-
-
-import java.io.File;
-
-import org.apache.http.entity.mime.MultipartEntity;
-import org.apache.http.entity.mime.content.FileBody;
-import org.apache.http.entity.mime.content.StringBody;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.sling.commons.json.JSONArray;
-import org.apache.sling.commons.json.JSONObject;
-import org.apache.sling.testing.tools.http.RequestBuilder;
-import org.apache.sling.testing.tools.http.RequestExecutor;
-import org.apache.sling.testing.tools.http.RetryingContentChecker;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** HTTP Client for the Felix webconsole - simplistic for now */
-public class WebconsoleClient {
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private final RequestExecutor executor;
-    private final RequestBuilder builder;
-    private final String username;
-    private final String password;
-    
-    public static final String JSON_KEY_ID = "id";
-    public static final String JSON_KEY_VERSION = "version";
-    public static final String JSON_KEY_DATA = "data";
-    public static final String JSON_KEY_STATE = "state";
-    public static final String CONSOLE_BUNDLES_PATH = "/system/console/bundles";
-    
-    public WebconsoleClient(String slingServerUrl, String username, String password) {
-        this.builder = new RequestBuilder(slingServerUrl);
-        this.executor = new RequestExecutor(new DefaultHttpClient());
-        this.username = username;
-        this.password = password;
-    }
-    
-    public void uninstallBundle(String symbolicName, File f) throws Exception {
-        final long bundleId = getBundleId(symbolicName);
-        
-        log.info("Uninstalling bundle {} with bundleId {}", symbolicName, bundleId);
-
-        final MultipartEntity entity = new MultipartEntity();
-        entity.addPart("action",new StringBody("uninstall"));
-        executor.execute(
-                builder.buildPostRequest(CONSOLE_BUNDLES_PATH+"/"+bundleId)
-                .withCredentials(username, password)
-                .withEntity(entity)
-        ).assertStatus(200);
-    }
-    
-    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
-    public void installBundle(File f, boolean startBundle) throws Exception {
-        installBundle(f, startBundle, 0);
-    }
-    
-    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
-    public void installBundle(File f, boolean startBundle, int startLevel) throws Exception {
-        
-        // Setup request for Felix Webconsole bundle install
-        final MultipartEntity entity = new MultipartEntity();
-        entity.addPart("action",new StringBody("install"));
-        if(startBundle) {
-            entity.addPart("bundlestart", new StringBody("true"));
-        }
-        entity.addPart("bundlefile", new FileBody(f));
-        
-        if(startLevel > 0) {
-            entity.addPart("bundlestartlevel", new StringBody(String.valueOf(startLevel)));
-            log.info("Installing bundle {} at start level {}", f.getName(), startLevel);
-        } else {
-            log.info("Installing bundle {} at default start level", f.getName());
-        }
-        
-        // Console returns a 302 on success (and in a POST this
-        // is not handled automatically as per HTTP spec)
-        executor.execute(
-                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
-                .withCredentials(username, password)
-                .withEntity(entity)
-        ).assertStatus(302);
-    }
-    
-    /** Check that specified bundle is installed - must be called
-     *  before other methods that take a symbolicName parameter, 
-     *  in case installBundle was just called and the actual 
-     *  installation hasn't happened yet. */
-    public void checkBundleInstalled(String symbolicName, int timeoutSeconds) {
-        final String path = getBundlePath(symbolicName, ".json");
-        new RetryingContentChecker(executor, builder, username, password).check(path, 200, timeoutSeconds, 500);
-    }
-    
-    private JSONObject getBundleData(String symbolicName) throws Exception {
-        // This returns a data structure like
-        // {"status":"Bundle information: 173 bundles in total - all 173 bundles active.","s":[173,171,2,0,0],"data":
-        //  [
-        //      {"id":0,"name":"System Bundle","fragment":false,"stateRaw":32,"state":"Active","version":"3.0.7","symbolicName":"org.apache.felix.framework","category":""},
-        //  ]}
-        final String path = getBundlePath(symbolicName, ".json");
-        final String content = executor.execute(
-                builder.buildGetRequest(path)
-                .withCredentials(username, password)
-        ).assertStatus(200)
-        .getContent();
-        
-        final JSONObject root = new JSONObject(content);
-        if(!root.has(JSON_KEY_DATA)) {
-            throw new Exception(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
-        }
-        final JSONArray data = root.getJSONArray(JSON_KEY_DATA);
-        if(data.length() < 1) {
-            throw new Exception(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
-        }
-        final JSONObject bundle = data.getJSONObject(0);
-        if(!bundle.has(JSON_KEY_STATE)) {
-            throw new Exception(path + ".data[0].state missing, JSON content=" + content);
-        }
-        return bundle;
-    }
-
-    /** Get bundle id */
-    public long getBundleId(String symbolicName) throws Exception {
-        final JSONObject bundle = getBundleData(symbolicName);
-        return bundle.getLong(JSON_KEY_ID);
-    }
-    
-    /** Get bundle version **/
-    public String getBundleVersion(String symbolicName) throws Exception {
-        final JSONObject bundle = getBundleData(symbolicName);
-        return bundle.getString(JSON_KEY_VERSION);
-    }
-    
-    /** Get specified bundle state */
-    public String getBundleState(String symbolicName) throws Exception {
-        final JSONObject bundle = getBundleData(symbolicName);
-        return bundle.getString(JSON_KEY_STATE);
-    }
-    
-    /** Start specified bundle */
-    public void startBundle(String symbolicName) throws Exception {
-        // To start the bundle we POST action=start to its URL
-        final String path = getBundlePath(symbolicName, null);
-        log.info("Starting bundle {} via {}", symbolicName, path);
-        
-        final MultipartEntity entity = new MultipartEntity();
-        entity.addPart("action",new StringBody("start"));
-        executor.execute(
-                builder.buildPostRequest(path)
-                .withCredentials(username, password)
-                .withEntity(entity)
-        ).assertStatus(200);
-    }
-    
-    private String getBundlePath(String symbolicName, String extension) {
-        return CONSOLE_BUNDLES_PATH + "/" + symbolicName 
-        + (extension == null ? "" : extension);
-    }
-
-    /** Calls PackageAdmin.refreshPackages to enforce re-wiring of all bundles. */
-    public void refreshPackages() throws Exception {
-        log.info("Refresh packages.");
-
-        final MultipartEntity entity = new MultipartEntity();
-        entity.addPart("action", new StringBody("refreshPackages"));
-
-        executor.execute(
-                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
-                .withCredentials(username, password)
-                .withEntity(entity)
-        ).assertStatus(200);
-    }
-    
-}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
new file mode 100644
index 0000000..c7b50cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.testing.clients.util.poller;
+
+import org.apache.sling.testing.clients.AbstractSlingClient;
+import org.apache.sling.testing.clients.ClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Allows polling for a resource
+ */
+public class PathPoller extends AbstractPoller {
+    private static final Logger LOG = LoggerFactory.getLogger(PathPoller.class);
+    private final AbstractSlingClient client;
+    private final String path;
+    private final int[] expectedStatus;
+    private Exception exception;
+
+    public PathPoller(AbstractSlingClient client, String path, long waitInterval, long waitCount, int... expectedStatus) {
+        super(waitInterval, waitCount);
+        this.client = client;
+        this.path = path;
+        this.expectedStatus = expectedStatus;
+    }
+
+
+    @Override
+    public boolean call() {
+        return true;
+    }
+
+    @Override
+    public boolean condition() {
+        try {
+            client.doGet(path, expectedStatus);
+        } catch (ClientException e) {
+            LOG.warn("Get on {} failed: {}", path, e);
+            this.exception = e;
+            return false;
+        }
+        return true;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 05/37: SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code * Added TimeoutsProvider

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 5f3c3cfcd2c94bc25b03c7bf2c6d562a2bde7858
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Wed Jun 15 15:01:58 2016 +0000

    SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code
     * Added TimeoutsProvider
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1748594 13f79535-47bb-0310-9956-ffa450edef68
---
 .../testing/clients/util/TimeoutsProvider.java     | 79 ++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
new file mode 100644
index 0000000..4964300
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.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.testing.clients.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Return timeout values that can be multiplied by a configurable
+ *  factor. Useful to cope with slower integration testing systems:
+ *  use timeout constants in your code that work for usual development
+ *  systems, and set a multiplier when running on a slower system.
+ */
+public class TimeoutsProvider {
+    private static final Logger log = LoggerFactory.getLogger(TimeoutsProvider.class);
+    public static final String PROP_TIMEOUT_MULTIPLIER = "sling.testing.timeout.multiplier";
+    private static float timeoutFactor = -1;
+    private static TimeoutsProvider INSTANCE;
+    
+    private TimeoutsProvider() {
+        if(timeoutFactor < 0) {
+            timeoutFactor = 1;
+            final String str = System.getProperty(PROP_TIMEOUT_MULTIPLIER);
+            if(str != null) {
+                try {
+                    timeoutFactor = Float.valueOf(str.trim());
+                    log.info("Timeout factor set to {} from system property {}", 
+                            timeoutFactor, PROP_TIMEOUT_MULTIPLIER);
+                } catch(NumberFormatException nfe) {
+                    throw new IllegalStateException("Invalid timeout factor: " + PROP_TIMEOUT_MULTIPLIER + "=" + str);
+                }
+            }
+        }
+    }
+    
+    public static TimeoutsProvider getInstance() {
+        if(INSTANCE == null) {
+            synchronized (TimeoutsProvider.class) {
+                INSTANCE = new TimeoutsProvider();
+            }
+        }
+        return INSTANCE;
+    }
+    
+    public long getTimeout(long nomimalValue) {
+        final long result = (long)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    public int getTimeout(int nomimalValue) {
+        final int result = (int)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    /**
+     * Get timeout from a system property, with default value
+     */
+    public int getTimeout(String systemPropertyName, int defaultNominalValue) {
+        int result = defaultNominalValue;
+        final String str = System.getProperty(systemPropertyName);
+        if(str != null) {
+            result = Integer.parseInt(str);
+        }
+        return getTimeout(result);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 11/37: @releng updated dependency in org.apache.sling.testing.clients to hapi client 1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 9e4c3097a249e14e75877c7a7a76c8916053457c
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Mon Sep 19 09:51:30 2016 +0000

    @releng updated dependency in org.apache.sling.testing.clients to hapi client 1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1761413 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 471bbcf..a77f01f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -151,7 +151,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.hapi.client</artifactId>
-            <version>1.0.1-SNAPSHOT</version>
+            <version>1.0.0</version>
         </dependency>
     </dependencies>
 </project>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 04/37: SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 3c75f9f47a82249bdd7d5965151c42f4be5c81f0
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Wed Jun 15 15:00:53 2016 +0000

    SLING-5727 Remove o.a.s.testing.tools dependency in o.a.s.testing.serversetup and adapt http code
    
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1748593 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                          | 9 ++++++++-
 .../org/apache/sling/testing/clients/osgi/BundlesInstaller.java  | 2 +-
 .../org/apache/sling/testing/clients/util/poller/PathPoller.java | 6 +++++-
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index b67851d..83e0533 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,7 +55,14 @@
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.sling.testing.clients.*,
+                            org.apache.sling.testing.clients,
+                            org.apache.sling.testing.clients.instance,
+                            org.apache.sling.testing.clients.interceptors,
+                            org.apache.sling.testing.clients.osgi,
+                            org.apache.sling.testing.clients.util,
+                            org.apache.sling.testing.clients.util.config,
+                            org.apache.sling.testing.clients.util.poller,
+                            org.apache.sling.testing.clients.*
                         </Export-Package>
                         <Import-Package>
                             org.apache.commons.exec.*; resolution:=optional,
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
index e99c454..8a19aa6 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
@@ -46,7 +46,7 @@ public class BundlesInstaller {
      * @throws IOException
      * @throws InterruptedException
      */
-    public boolean isInstalled(File bundleFile) throws ClientException, InterruptedException, IOException {
+    public boolean isInstalled(File bundleFile) throws InterruptedException, IOException {
         final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
         log.debug("Checking if installed: " + bundleSymbolicName);
         boolean installed = osgiConsoleClient.checkBundleInstalled(bundleSymbolicName, 1000, 1);
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
index c7b50cb..61a1ee1 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
@@ -37,7 +37,11 @@ public class PathPoller extends AbstractPoller {
         super(waitInterval, waitCount);
         this.client = client;
         this.path = path;
-        this.expectedStatus = expectedStatus;
+        if (null == expectedStatus || expectedStatus.length == 0) {
+            this.expectedStatus = new int[]{200};
+        } else {
+            this.expectedStatus = expectedStatus;
+        }
     }
 
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 18/37: SLING-6431 and some cosmetic changes

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 598c44e0f1d178d1c9b71409930f6f5f6275b0b1
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Fri Jan 6 09:25:24 2017 +0000

    SLING-6431 and some cosmetic changes
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1777545 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  6 +++---
 .../sling/testing/clients/AbstractSlingClient.java | 12 ++++++++++-
 .../apache/sling/testing/clients/SlingClient.java  |  4 ++++
 .../sling/testing/clients/html/package-info.java   |  2 +-
 .../clients/instance/InstanceConfiguration.java    | 21 ++++++++++++++++++-
 .../testing/clients/instance/InstanceSetup.java    | 24 +++++++++++++++++-----
 .../clients/{ => instance}/package-info.java       |  2 +-
 .../interceptors/TestDescriptionInterceptor.java   |  2 +-
 .../sling/testing/clients/osgi/package-info.java   |  2 +-
 .../apache/sling/testing/clients/package-info.java |  2 +-
 .../{ => util/config/impl}/package-info.java       |  4 ++--
 .../clients/{ => util/poller}/package-info.java    |  4 ++--
 12 files changed, 66 insertions(+), 19 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1267c96..4a326bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>26</version>
+        <version>28</version>
         <relativePath />
     </parent>
 
@@ -77,7 +77,7 @@
     <dependencies>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
+            <artifactId>osgi.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -151,7 +151,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.hapi.client</artifactId>
-            <version>1.0.1-SNAPSHOT</version>
+            <version>1.0.0</version>
         </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java b/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
index 24d731f..51c57d8 100644
--- a/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
@@ -28,6 +28,7 @@ import org.apache.http.protocol.HttpContext;
 import org.apache.sling.testing.clients.util.HttpUtils;
 import org.slf4j.LoggerFactory;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.net.URI;
@@ -41,7 +42,7 @@ import java.util.UUID;
  * The abstract base client for all implementing integration test clients.
  */
 @Immutable
-public class AbstractSlingClient implements HttpClient {
+public class AbstractSlingClient implements HttpClient, Closeable {
 
     private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
 
@@ -648,6 +649,15 @@ public class AbstractSlingClient implements HttpClient {
         return doRequest(request, headers, expectedStatus);
     }
 
+    @Override
+    /**
+     * <p>Closes the http client and makes sure all the underlying resources, like the connection manager, shut down </p>
+     *
+     */
+    public void close() throws IOException {
+        this.http.close();
+    }
+
 
     //
     // HttpClient  base methods
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClient.java b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
index 7d9bbe3..ede00b5 100644
--- a/src/main/java/org/apache/sling/testing/clients/SlingClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
@@ -21,6 +21,7 @@ import org.apache.http.HttpEntity;
 import org.apache.http.HttpRequestInterceptor;
 import org.apache.http.HttpResponseInterceptor;
 import org.apache.http.NameValuePair;
+import org.apache.http.annotation.Immutable;
 import org.apache.http.client.CookieStore;
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.RedirectStrategy;
@@ -31,6 +32,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 
 import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
+import org.apache.sling.testing.clients.interceptors.TestDescriptionInterceptor;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
 import org.apache.sling.testing.clients.util.JsonUtils;
@@ -51,6 +53,7 @@ import static org.apache.http.HttpStatus.SC_OK;
  * <p>It has methods to perform simple node operations on the server like creating and deleting nodes, etc.
  * on the server using requests. </p>
  */
+@Immutable
 public class SlingClient extends AbstractSlingClient {
 
     public static final String DEFAULT_NODE_TYPE = "sling:OrderedFolder";
@@ -557,6 +560,7 @@ public class SlingClient extends AbstractSlingClient {
             httpClientBuilder.setMaxConnPerRoute(10);
             httpClientBuilder.setMaxConnTotal(100);
             // Interceptors
+            httpClientBuilder.addInterceptorLast(new TestDescriptionInterceptor());
             httpClientBuilder.addInterceptorLast(new DelayRequestInterceptor(Constants.HTTP_DELAY));
 
             return this;
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
index 764e51d..97b22cb 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("2.0.0")
+@Version("2.1.0")
 package org.apache.sling.testing.clients.html;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java b/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
index 168e6ab..4081bd5 100644
--- a/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
+++ b/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
@@ -23,12 +23,23 @@ import java.net.URI;
  */
 public class InstanceConfiguration {
 
+    public static final String DEFAULT_ADMIN_USER = "admin";
+    public static final String DEFAULT_ADMIN_PASSWORD = "admin";
+
     private URI url;
     private final String runmode;
+    private String adminUser;
+    private String adminPassword;
 
-    public InstanceConfiguration(final URI url, final String runmode) {
+    public InstanceConfiguration(final URI url, final String runmode, String adminUser, String adminPassword) {
         this.url = url;
         this.runmode = runmode;
+        this.adminUser = adminUser;
+        this.adminPassword = adminPassword;
+    }
+
+    public InstanceConfiguration(URI url, String runmode) {
+        this(url, runmode, DEFAULT_ADMIN_USER, DEFAULT_ADMIN_PASSWORD);
     }
 
     public URI getUrl() {
@@ -38,4 +49,12 @@ public class InstanceConfiguration {
     public String getRunmode() {
         return runmode;
     }
+
+    public String getAdminUser() {
+        return adminUser;
+    }
+
+    public String getAdminPassword() {
+        return adminPassword;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java b/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
index de4e5a3..051b4c9 100644
--- a/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
+++ b/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
@@ -29,10 +29,16 @@ import java.util.List;
  * Utility class for getting the current instance setup
  */
 public final class InstanceSetup {
-
     private static final Logger LOG = LoggerFactory.getLogger(InstanceSetup.class);
     private static InstanceSetup SINGLETON;
 
+    // TODO: JAVADOC
+    public static final String INSTANCE_CONFIG_INSTANCES = Constants.CONFIG_PROP_PREFIX + "instances";
+    public static final String INSTANCE_CONFIG_URL = Constants.CONFIG_PROP_PREFIX + "instance.url.";
+    public static final String INSTANCE_CONFIG_RUNMODE = Constants.CONFIG_PROP_PREFIX + "instance.runmode.";
+    public static final String INSTANCE_CONFIG_ADMINUSER = Constants.CONFIG_PROP_PREFIX + "instance.adminUser.";
+    public static final String INSTANCE_CONFIG_ADMINPASSWORD = Constants.CONFIG_PROP_PREFIX + "instance.adminPassword.";
+
     /**
      * @return  the current setup object.
      */
@@ -46,18 +52,26 @@ public final class InstanceSetup {
     private final List<InstanceConfiguration> configs = new ArrayList<InstanceConfiguration>();
 
     private InstanceSetup() {
-        final int number = Integer.valueOf(System.getProperty(Constants.CONFIG_PROP_PREFIX + "instances", "0"));
+        final int number = Integer.valueOf(System.getProperty(INSTANCE_CONFIG_INSTANCES, "0"));
         for (int i=1; i<=number; i++ ) {
             URI url;
             try {
-                url = new URI(System.getProperty(Constants.CONFIG_PROP_PREFIX + "instance.url." + String.valueOf(i)));
+                url = new URI(System.getProperty(INSTANCE_CONFIG_URL + String.valueOf(i)));
             } catch (URISyntaxException e) {
                 LOG.error("Could not read URL for instance");
                 continue;
             }
-            final String runmode = System.getProperty(Constants.CONFIG_PROP_PREFIX + "instance.runmode." + String.valueOf(i));
+            final String runmode = System.getProperty(INSTANCE_CONFIG_RUNMODE + String.valueOf(i));
+            final String adminUser = System.getProperty(INSTANCE_CONFIG_ADMINUSER + String.valueOf(i));
+            final String adminPassword = System.getProperty(INSTANCE_CONFIG_ADMINPASSWORD + String.valueOf(i));
 
-            final InstanceConfiguration qc = new InstanceConfiguration(url, runmode);
+            final InstanceConfiguration qc;
+            // Only pass in the admin user name and password if they're both set
+            if ((null == adminUser) || (null == adminPassword)) {
+                qc = new InstanceConfiguration(url, runmode);
+            } else {
+                qc = new InstanceConfiguration(url, runmode, adminUser, adminPassword);
+            }
 
             this.configs.add(qc);
         }
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/instance/package-info.java
similarity index 94%
copy from src/main/java/org/apache/sling/testing/clients/package-info.java
copy to src/main/java/org/apache/sling/testing/clients/instance/package-info.java
index 406e979..43010e9 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/instance/package-info.java
@@ -18,7 +18,7 @@
  */
 
 @Version("1.1.0")
-package org.apache.sling.testing.clients;
+package org.apache.sling.testing.clients.instance;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
index ad91912..baad350 100644
--- a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
@@ -42,7 +42,7 @@ public class TestDescriptionInterceptor implements HttpRequestInterceptor{
     }
 
     private static void addHeader(HttpRequest httpRequest, String name, String value){
-        if (value != null){
+        if (value != null) {
             httpRequest.addHeader(name, value);
         }
     }
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 3deee0a..3a2f85e 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,5 +19,5 @@
 /**
  * OSGI testing tools.
  */
-@aQute.bnd.annotation.Version("1.0.0")
+@aQute.bnd.annotation.Version("1.1.0")
 package org.apache.sling.testing.clients.osgi;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
index 406e979..d9cf961 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.1.0")
+@Version("1.2.0")
 package org.apache.sling.testing.clients;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
similarity index 91%
copy from src/main/java/org/apache/sling/testing/clients/package-info.java
copy to src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
index 406e979..d6bfe66 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
@@ -17,8 +17,8 @@
  * under the License.
  */
 
-@Version("1.1.0")
-package org.apache.sling.testing.clients;
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util.config.impl;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
similarity index 92%
copy from src/main/java/org/apache/sling/testing/clients/package-info.java
copy to src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
index 406e979..6367401 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
@@ -17,8 +17,8 @@
  * under the License.
  */
 
-@Version("1.1.0")
-package org.apache.sling.testing.clients;
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util.poller;
 
 import aQute.bnd.annotation.Version;
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 34/37: SLING-7029 - Extension - adding method to stop a bundle

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 1f4e21ccee07c2d81246ab7d365053d1a7ee9fc6
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Aug 4 07:40:20 2017 +0000

    SLING-7029 - Extension - adding method to stop a bundle
    
    method to stop a bundle added.
    
    public void stopBundle(String symbolicName)
    
    Submitted-By: Andreea Dieaconu <an...@yahoo.com>
    
    Closes #249
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1804073 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/testing/clients/osgi/OsgiConsoleClient.java      | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 1253d76..a6df552 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -563,6 +563,18 @@ public class OsgiConsoleClient extends SlingClient {
         LOG.info("Starting bundle {} via {}", symbolicName, path);
         this.doPost(path, FormEntityBuilder.create().addParameter("action", "start").build(), SC_OK);
     }
+    
+    /**
+     * Stop a bundle
+     * @param symbolicName the name of the bundle
+     * @throws ClientException
+     */
+    public void stopBundle(String symbolicName) throws ClientException {
+        // To stop the bundle we POST action=stop to its URL
+        final String path = getBundlePath(symbolicName);
+        LOG.info("Stopping bundle {} via {}", symbolicName, path);
+        this.doPost(path, FormEntityBuilder.create().addParameter("action", "stop").build(), SC_OK);
+    }
 
 
     /**
@@ -731,4 +743,4 @@ public class OsgiConsoleClient extends SlingClient {
             return config;
         }
     }
-}
\ No newline at end of file
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 06/37: SLING-5793 Add client that can leverage hapi client in testing http clients

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 0d733dd4eccd337c23a23026f12efc96be98e2e8
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Thu Jun 16 12:36:48 2016 +0000

    SLING-5793 Add client that can leverage hapi client in testing http clients
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1748709 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  6 ++
 .../testing/clients/html/MicrodataClient.java      | 81 ++++++++++++++++++++++
 .../sling/testing/clients/html/package-info.java   | 24 +++++++
 3 files changed, 111 insertions(+)

diff --git a/pom.xml b/pom.xml
index 83e0533..026f356 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
                     <instructions>
                         <Export-Package>
                             org.apache.sling.testing.clients,
+                            org.apache.sling.testing.clients.html,
                             org.apache.sling.testing.clients.instance,
                             org.apache.sling.testing.clients.interceptors,
                             org.apache.sling.testing.clients.osgi,
@@ -153,5 +154,10 @@
             <version>4.12</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.hapi.client</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java b/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java
new file mode 100644
index 0000000..810a28c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/html/MicrodataClient.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.testing.clients.html;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.hapi.client.HtmlClient;
+import org.apache.sling.hapi.client.microdata.MicrodataDocument;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.apache.sling.testing.clients.SlingClientConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+
+public class MicrodataClient extends SlingClient implements HtmlClient {
+    protected static final Logger LOG = LoggerFactory.getLogger(MicrodataClient.class);
+
+    public MicrodataClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        super(http, config);
+    }
+
+    public MicrodataClient(URI url, String user, String password) throws ClientException {
+        super(url, user, password);
+    }
+
+    @Override
+    public MicrodataDocument enter(String url) throws org.apache.sling.hapi.client.ClientException {
+        return get(url);
+    }
+
+    @Override
+    public MicrodataDocument get(String url) throws org.apache.sling.hapi.client.ClientException {
+        try {
+            return newDocument(doGet(url).getContent());
+        } catch (ClientException e) {
+            throw new org.apache.sling.hapi.client.ClientException("Cannot create Microdata document", e);
+        }
+    }
+
+    @Override
+    public MicrodataDocument post(String url, HttpEntity entity) throws org.apache.sling.hapi.client.ClientException {
+        try {
+            return newDocument(doPost(url, entity).getContent());
+        } catch (ClientException e) {
+            throw new org.apache.sling.hapi.client.ClientException("Cannot create Microdata document", e);
+        }
+    }
+
+    @Override
+    public MicrodataDocument delete(String url) throws org.apache.sling.hapi.client.ClientException {
+        try {
+            return newDocument(doDelete(url, null, null).getContent());
+        } catch (ClientException e) {
+            throw new org.apache.sling.hapi.client.ClientException("Cannot create Microdata document", e);
+        }
+    }
+
+    @Override
+    public MicrodataDocument newDocument(String html) {
+        return new MicrodataDocument(html, this, this.getUrl().toString());
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
new file mode 100644
index 0000000..705d7ac
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.html;
+
+import aQute.bnd.annotation.Version;
+

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 12/37: @releng changed package versions in org.apache.sling.testing.clients

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 114c5faa9eff147c86365f669574335e3d5b4c50
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Mon Sep 19 14:07:38 2016 +0000

    @releng changed package versions in org.apache.sling.testing.clients
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1761442 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                               | 2 +-
 src/main/java/org/apache/sling/testing/clients/html/package-info.java | 2 +-
 src/main/java/org/apache/sling/testing/clients/package-info.java      | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index a77f01f..9a75bf1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
         <relativePath />
     </parent>
 
-    <artifactId>org.apache.sling.testing.clients</artifactId>
+        <artifactId>org.apache.sling.testing.clients</artifactId>
     <version>0.1.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
index 705d7ac..764e51d 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("2.0.0")
 package org.apache.sling.testing.clients.html;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
index ec6c85a..406e979 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.testing.clients;
 
 import aQute.bnd.annotation.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 15/37: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 295db9a9e7afd90fd489d38256bcab7ce5d55515
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Mon Sep 19 14:14:53 2016 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1761447 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 31f4176..1d09323 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.0.0</version>
+    <version>1.0.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.0</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.0</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.0.0</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 30/37: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 4be7f2b018cf9c3d4f8bd663ac2a1ef321f35f31
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 15 13:03:54 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798828 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1ff3322..836e0c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.2</version>
+    <version>1.1.3-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.2</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.2</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.2</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 28/37: SLING-6954 - Add client support for the org.apache.sling.testing.email bundle

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit bddb9f839284ccad7da7f48ffc8855fca3609fb7
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Jun 13 13:13:43 2017 +0000

    SLING-6954 - Add client support for the org.apache.sling.testing.email
    bundle
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798597 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/testing/clients/email/EmailMessage.java  |  36 +++++++
 .../testing/clients/email/SlingEmailClient.java    | 113 +++++++++++++++++++++
 .../sling/testing/clients/email/package-info.java  |  23 +++++
 3 files changed, 172 insertions(+)

diff --git a/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java b/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java
new file mode 100644
index 0000000..e013e51
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.clients.email;
+
+/**
+ * Holds information retrieved from the mock SMTP server deployed in Sling
+ *
+ */
+public final class EmailMessage {
+	
+	private String content;
+
+	public EmailMessage(String content) {
+		this.content = content;
+	}
+	
+	public String getContent() {
+		return content;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java b/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java
new file mode 100644
index 0000000..65bdaa1
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java
@@ -0,0 +1,113 @@
+/*
+ * 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.testing.clients.email;
+
+import static org.apache.http.HttpStatus.SC_NO_CONTENT;
+import static org.apache.http.HttpStatus.SC_OK;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.apache.sling.testing.clients.SlingClientConfig;
+import org.apache.sling.testing.clients.SlingHttpResponse;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+
+/**
+ * Accesses email stored by a mock SMTP server deployed to Sling 
+ *
+ * <p>Requires that the <tt>org.apache.sling.testing.email</tt> bundle is deployed.</p>
+ */
+public final class SlingEmailClient extends SlingClient {
+
+	/**
+	 * The well-known path under which the EmailServlet is deployed
+	 */
+	private static final String EMAIL_SERVLET_PATH = "/system/sling/testing/email";
+	
+	/**
+	 * The well-known property name of the email body contents
+	 */
+	private static final String PN_CONTENT = "-Content-";
+	
+	
+	private final ObjectMapper mapper = new ObjectMapper();
+
+	public SlingEmailClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+		super(http, config);
+	}
+	
+	/**
+	 * Retrieves the actual bind port of the SMTP server
+	 * 
+	 * @return the port value
+	 * @throws ClientException in case of any errors
+	 */
+	public int getBindPort() throws ClientException {
+        try {
+			SlingHttpResponse mockEmailConfig = doGet(EMAIL_SERVLET_PATH + "/config", SC_OK);
+			
+			JsonNode configNode = mapper.readTree(mockEmailConfig.getContent());
+			return configNode.get("bindPort").getIntValue();
+		} catch (IOException e) {
+			throw new ClientException("Failed retrieving configuration", e);
+		}
+	}
+	
+	/**
+	 * Retrieves the list of mail messages currently stored
+	 * 
+	 * @return the list of messages, possibly empty
+	 * @throws ClientException in case of any errors
+	 */
+	public List<EmailMessage> getMessages() throws ClientException {
+    	List<EmailMessage> emails = new ArrayList<>();
+    	
+        try {
+			SlingHttpResponse response = doGet(EMAIL_SERVLET_PATH + "/messages", SC_OK);
+			JsonNode messages = mapper.readTree(response.getContent());
+			for ( JsonNode emailNode : messages.get("messages") ) {
+				EmailMessage msg = new EmailMessage(emailNode.get(PN_CONTENT).getTextValue());
+				emails.add(msg);
+			}
+		} catch (IOException e) {
+			throw new ClientException("Failed retrieving email messages", e);
+		}
+        
+        
+        return emails;		
+	}
+	
+	/**
+	 * Deletes all mail messages currently stored
+	 * 
+	 * @throws ClientException in case of any errors
+	 */
+	public void deleteMessages() throws ClientException {
+		doDelete(EMAIL_SERVLET_PATH, Collections.<NameValuePair>emptyList(), 
+				Collections.<Header> emptyList(), SC_NO_CONTENT);		
+	}
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/email/package-info.java b/src/main/java/org/apache/sling/testing/clients/email/package-info.java
new file mode 100644
index 0000000..373bccc
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/email/package-info.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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.email;
+
+import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 21/37: use Sling Parent 30

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 2ba56329a601eb02579882998ccf4321b5ca867c
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Mon Mar 6 10:31:22 2017 +0000

    use Sling Parent 30
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1785622 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 17392ee..af7f357 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>28</version>
+        <version>30</version>
         <relativePath />
     </parent>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 33/37: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit faa7305571d073e524776753be56e3611907f05f
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 15 16:19:39 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798856 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index aea08a5..2b3dc8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.4</version>
+    <version>1.1.5-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.4</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.4</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.4</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 14/37: [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 200bc43da918bae68aa16407f60a3bd89d98db00
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Mon Sep 19 14:14:38 2016 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1761445 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index a77f01f..31f4176 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>0.1.0-SNAPSHOT</version>
+    <version>1.0.0</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.0</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.0.0</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 25/37: SLING-6905: Remove commons.json from testing http clients by using already used jackson library. Patch provided by Valentin Olteanu. This closes #235.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit dd1dfb8aa62333fea79fd34ebcec5f5c46bcbc53
Author: Karl Pauls <pa...@apache.org>
AuthorDate: Tue May 30 08:41:19 2017 +0000

    SLING-6905: Remove commons.json from testing http clients by using already used jackson library. Patch provided by Valentin Olteanu. This closes #235.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1796802 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  5 --
 .../testing/clients/osgi/OsgiConsoleClient.java    | 72 +++++++++++-----------
 2 files changed, 35 insertions(+), 42 deletions(-)

diff --git a/pom.xml b/pom.xml
index be389da..1b42b85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -131,11 +131,6 @@
             <artifactId>org.apache.sling.xss</artifactId>
             <version>1.0.4</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.16</version>
-        </dependency>
 
         <!-- For tests -->
         <dependency>
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 8dd8ca2..1253d76 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -20,9 +20,6 @@ package org.apache.sling.testing.clients.osgi;
 import org.apache.http.Header;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.sling.commons.json.JSONArray;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.testing.clients.ClientException;
 import org.apache.sling.testing.clients.SlingClient;
 import org.apache.sling.testing.clients.SlingClientConfig;
@@ -511,12 +508,14 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the id cannot be retrieved
      */
     public long getBundleId(String symbolicName) throws ClientException {
-        final JSONObject bundle = getBundleData(symbolicName);
-        try {
-            return bundle.getLong(JSON_KEY_ID);
-        } catch (JSONException e) {
-            throw new ClientException("Cannot get id from json", e);
+        final JsonNode bundle = getBundleData(symbolicName);
+        final JsonNode idNode = bundle.get(JSON_KEY_ID);
+
+        if (idNode == null) {
+            throw new ClientException("Cannot get id from bundle json");
         }
+
+        return idNode.getLongValue();
     }
 
     /**
@@ -526,12 +525,14 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException
      */
     public String getBundleVersion(String symbolicName) throws ClientException {
-        final JSONObject bundle = getBundleData(symbolicName);
-        try {
-            return bundle.getString(JSON_KEY_VERSION);
-        } catch (JSONException e) {
-            throw new ClientException("Cannot get version from json", e);
+        final JsonNode bundle = getBundleData(symbolicName);
+        final JsonNode versionNode = bundle.get(JSON_KEY_VERSION);
+
+        if (versionNode == null) {
+            throw new ClientException("Cannot get version from bundle json");
         }
+
+        return versionNode.getTextValue();
     }
 
     /**
@@ -541,12 +542,14 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the state cannot be retrieved
      */
     public String getBundleState(String symbolicName) throws ClientException {
-        final JSONObject bundle = getBundleData(symbolicName);
-        try {
-            return bundle.getString(JSON_KEY_STATE);
-        } catch (JSONException e) {
-            throw new ClientException("Cannot get state from json", e);
+        final JsonNode bundle = getBundleData(symbolicName);
+        final JsonNode stateNode = bundle.get(JSON_KEY_STATE);
+
+        if (stateNode == null) {
+            throw new ClientException("Cannot get state from bundle json");
         }
+
+        return stateNode.getTextValue();
     }
 
     /**
@@ -635,31 +638,26 @@ public class OsgiConsoleClient extends SlingClient {
      *   }]
      * }
      */
-    private JSONObject getBundleData(String symbolicName) throws ClientException {
+    private JsonNode getBundleData(String symbolicName) throws ClientException {
         final String path = getBundlePath(symbolicName, ".json");
         final String content = this.doGet(path, SC_OK).getContent();
+        final JsonNode root = JsonUtils.getJsonNodeFromString(content);
 
-        try {
-            final JSONObject root = new JSONObject(content);
-
-            if (!root.has(JSON_KEY_DATA)) {
-                throw new ClientException(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
-            }
-
-            final JSONArray data = root.getJSONArray(JSON_KEY_DATA);
-            if (data.length() < 1) {
-                throw new ClientException(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
-            }
+        if (root.get(JSON_KEY_DATA) == null) {
+            throw new ClientException(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
+        }
 
-            final JSONObject bundle = data.getJSONObject(0);
-            if (!bundle.has(JSON_KEY_STATE)) {
-                throw new ClientException(path + ".data[0].state missing, JSON content=" + content);
-            }
+        Iterator<JsonNode> data = root.get(JSON_KEY_DATA).getElements();
+        if (!data.hasNext()) {
+            throw new ClientException(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
+        }
 
-            return bundle;
-        } catch (JSONException e) {
-            throw new ClientException("Cannot get json", e);
+        final JsonNode bundle = data.next();
+        if (bundle.get(JSON_KEY_STATE) == null) {
+            throw new ClientException(path + ".data[0].state missing, JSON content=" + content);
         }
+
+        return bundle;
     }
 
     //

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 23/37: SLING-6853 Improve polling capabilities in o.a.s.testing.clients thanks @volteanu for the contribution!

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit bd20f6abe2d98afeaee775e2cc7d9c5b7bc67aa1
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Tue May 16 12:34:17 2017 +0000

    SLING-6853 Improve polling capabilities in o.a.s.testing.clients
    thanks @volteanu for the contribution!
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1795303 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   9 +-
 .../apache/sling/testing/clients/SlingClient.java  |  88 ++++++--
 .../sling/testing/clients/html/package-info.java   |   2 +-
 .../testing/clients/osgi/BundlesInstaller.java     | 146 ++++++-------
 .../testing/clients/osgi/OsgiConsoleClient.java    | 241 ++++++++++++++++-----
 .../testing/clients/osgi/OsgiInstanceConfig.java   |  28 ++-
 .../sling/testing/clients/osgi/package-info.java   |   2 +-
 .../apache/sling/testing/clients/package-info.java |   2 +-
 .../testing/clients/util/TimeoutsProvider.java     |   4 +
 .../clients/util/config/InstanceConfig.java        |   4 +-
 .../util/config/impl/InstanceConfigCacheImpl.java  |   4 +-
 .../clients/util/poller/AbstractPoller.java        |   5 +
 .../testing/clients/util/poller/PathPoller.java    |   1 +
 .../sling/testing/clients/util/poller/Poller.java  |   4 +
 .../testing/clients/util/poller/package-info.java  |   2 +-
 15 files changed, 369 insertions(+), 173 deletions(-)

diff --git a/pom.xml b/pom.xml
index ff471a4..be389da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.0.2-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -149,5 +149,12 @@
             <artifactId>org.apache.sling.hapi.client</artifactId>
             <version>1.0.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.3</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClient.java b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
index ede00b5..bee5b40 100644
--- a/src/main/java/org/apache/sling/testing/clients/SlingClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
@@ -30,19 +30,20 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
-
 import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
 import org.apache.sling.testing.clients.interceptors.TestDescriptionInterceptor;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
 import org.apache.sling.testing.clients.util.JsonUtils;
 import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.codehaus.jackson.JsonNode;
 
 import java.io.File;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 import static org.apache.http.HttpStatus.SC_CREATED;
 import static org.apache.http.HttpStatus.SC_OK;
@@ -212,12 +213,16 @@ public class SlingClient extends AbstractSlingClient {
     /**
      * <p>Checks whether a path exists or not by making a GET request to that path with the {@code json extension} </p>
      * <p>It polls the server and waits until the path exists </p>
+     *
+     * @deprecated use {@link #waitExists(String, long, long)} instead.
+     *
      * @param path path to be checked
      * @param waitMillis time to wait between retries
      * @param retryCount number of retries before throwing an exception
      * @throws ClientException if the path was not found
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public void waitUntilExists(final String path, final long waitMillis, int retryCount)
             throws ClientException, InterruptedException {
         AbstractPoller poller =  new AbstractPoller(waitMillis, retryCount) {
@@ -244,6 +249,34 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
+     * <p>Waits until a path exists by making successive GET requests to that path with the {@code json extension} </p>
+     * <p>Polls the server until the path exists or until timeout is reached </p>
+     * @param path path to be checked
+     * @param timeout max total time to wait, in milliseconds
+     * @param delay time to wait between checks, in milliseconds
+     * @throws TimeoutException if the path was not found before timeout
+     * @throws InterruptedException to mark this operation as "waiting", should be rethrown by callers
+     * @since 1.1.0
+     */
+    public void waitExists(final String path, final long timeout, final long delay)
+            throws TimeoutException, InterruptedException {
+
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                return exists(path);
+            }
+
+            @Override
+            protected String message() {
+                return "Path " + path + " does not exist after %1$d ms";
+            }
+        };
+
+        p.poll(timeout, delay);
+    }
+
+    /**
      * Sets String component property on a node.
      *
      * @param nodePath       path to the node to be edited
@@ -303,12 +336,14 @@ public class SlingClient extends AbstractSlingClient {
      * Returns the JSON content of a node already mapped to a {@link org.codehaus.jackson.JsonNode}.<br>
      * Waits max 10 seconds for the node to be created.
      *
+     * @deprecated use {@link #waitExists(String, long, long)} and {@link #doGetJson(String, int, int...)} instead
      * @param path  the path to the content node
      * @param depth the number of levels to go down the tree, -1 for infinity
      * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
      * @throws ClientException if something fails during request/response processing
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public JsonNode getJsonNode(String path, int depth) throws ClientException, InterruptedException {
         return getJsonNode(path, depth, 500, 20);
     }
@@ -316,6 +351,7 @@ public class SlingClient extends AbstractSlingClient {
     /**
      * Returns JSON format of a content node already mapped to a {@link org.codehaus.jackson.JsonNode}.
      *
+     * @deprecated use {@link #waitExists(String, long, long)} and {@link #doGetJson(String, int, int...)} instead
      * @param path                 the path to the content node
      * @param depth                the number of levels to go down the tree, -1 for infinity
      * @param waitMillis           how long it should wait between requests
@@ -326,6 +362,7 @@ public class SlingClient extends AbstractSlingClient {
      * @throws ClientException if something fails during request/response cycle
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public JsonNode getJsonNode(String path, int depth, final long waitMillis, final int retryNumber, int... expectedStatus)
             throws ClientException, InterruptedException {
 
@@ -347,6 +384,31 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
+     * Returns the {@link org.codehaus.jackson.JsonNode} object corresponding to a content node.
+     *
+     * @param path the path to the content node
+     * @param depth the number of levels to go down the tree, -1 for infinity
+     * @param expectedStatus list of allowed HTTP Status to be returned. If not set, 200 (OK) is assumed.
+     *
+     * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
+     * @throws ClientException if the path does not exist or something fails during request/response cycle
+     * @since 1.1.0
+     */
+    public JsonNode doGetJson(String path, int depth, int... expectedStatus) throws ClientException {
+
+        // check for infinity
+        if (depth == -1) {
+            path += ".infinity.json";
+        } else {
+            path += "." + depth + ".json";
+        }
+
+        // request the JSON for the node
+        SlingHttpResponse response = this.doGet(path, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return JsonUtils.getJsonNodeFromString(response.getContent());
+    }
+
+    /**
      * Uploads a file to the repository. It creates a leaf node typed {@code nt:file}. The intermediary nodes are created with
      * type "sling:OrderedFolder" if parameter {@code createFolders} is true
      *
@@ -413,33 +475,31 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
-     * Get uuid from any repository path
+     * Get the UUID of a repository path
      *
-     * @param repPath path in repository
-     * @return uuid as String
+     * @param path path in repository
+     * @return uuid as String or null if path does not exist
      * @throws ClientException if something fails during request/response cycle
-     * @throws InterruptedException to mark this operation as "waiting"
      */
-    public String getUUID(String repPath) throws ClientException, InterruptedException {
-        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
-        if (!exists(repPath)) {
+    public String getUUID(String path) throws ClientException {
+        if (!exists(path)) {
             return null;
         }
-        JsonNode jsonNode = getJsonNode(repPath, -1);
+        JsonNode jsonNode = doGetJson(path, -1);
         return getUUId(jsonNode);
     }
 
     /**
-     * Get uuid from any repository path
+     * Get the UUID from a node that was already parsed in a {@link JsonNode}
      *
-     * @param jsonNode {@link JsonNode} in repository
-     * @return uuid as String or null if jsonNode is null or if the uuid was not found
+     * @param jsonNode {@link JsonNode} object of the repository node
+     * @return UUID as String or null if jsonNode is null or if the UUID was not found
      * @throws ClientException if something fails during request/response cycle
      */
+    // TODO make this method static
     public String getUUId(JsonNode jsonNode) throws ClientException {
-        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
         if (jsonNode == null) {
-            return null;  // node does not exist
+            return null;
         }
 
         JsonNode uuidNode = jsonNode.get("jcr:uuid");
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
index 5f5a9f5..cd55c59 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("2.1.0")
+@Version("2.2.0")
 package org.apache.sling.testing.clients.html;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
index 8a19aa6..fcfd6c5 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
@@ -17,13 +17,14 @@
 package org.apache.sling.testing.clients.osgi;
 
 import org.apache.sling.testing.clients.ClientException;
-import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -40,31 +41,34 @@ public class BundlesInstaller {
 
     /**
      * Checks if a bundle is installed or not. Does not retry.
-     * @param bundleFile
-     * @return
-     * @throws ClientException
-     * @throws IOException
-     * @throws InterruptedException
+     * @param bundleFile bundle file
+     * @return true if the bundle is installed
+     * @throws ClientException if the state of the bundle could not be determined
      */
-    public boolean isInstalled(File bundleFile) throws InterruptedException, IOException {
-        final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
-        log.debug("Checking if installed: " + bundleSymbolicName);
-        boolean installed = osgiConsoleClient.checkBundleInstalled(bundleSymbolicName, 1000, 1);
-        // if this succeeds, then there's no need to install again
-        if (installed) {
+    public boolean isInstalled(File bundleFile) throws ClientException {
+        String bundleSymbolicName = "";
+        try {
+            bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
+            log.debug("Checking if installed: " + bundleSymbolicName);
+
+            osgiConsoleClient.getBundleState(bundleSymbolicName);
             log.debug("Already installed: " + bundleSymbolicName);
             return true;
-        } else {
+        } catch (ClientException e) {
             log.debug("Not yet installed: " + bundleSymbolicName);
             return false;
+        } catch (IOException e) {
+            log.debug("Failed to retrieve bundle symbolic name from file. ", e);
+            throw new ClientException("Failed to retrieve bundle symbolic name from file. ", e);
         }
     }
 
     /**
      * Check if the installed version matches the one of the bundle (file)
-     * @param bundleFile
-     * @return
-     * @throws Exception
+     * @param bundleFile bundle file
+     * @return true if the bundle is installed and has the same version
+     * @throws ClientException if the installed version cannot be retrieved
+     * @throws IOException if the file version cannot be read
      */
     public boolean isInstalledWithSameVersion(File bundleFile) throws ClientException, IOException {
         final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
@@ -81,11 +85,11 @@ public class BundlesInstaller {
 
     /**
      * Install a list of bundles supplied as Files
-     * @param toInstall
-     * @param startBundles
-     * @throws Exception
+     * @param toInstall list ob bundles to install
+     * @param startBundles whether to start the bundles
+     * @throws ClientException if an error occurs during installation
      */
-    public void installBundles(List<File> toInstall, boolean startBundles) throws ClientException, IOException, InterruptedException {
+    public void installBundles(List<File> toInstall, boolean startBundles) throws ClientException, IOException {
         for(File f : toInstall) {
             final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
@@ -112,12 +116,11 @@ public class BundlesInstaller {
 
     /**
      * Uninstall a list of bundles supplied as Files
-     * @param toUninstall
-     * @throws ClientException
-     * @throws IOException
-     * @throws InterruptedException
+     * @param toUninstall bundles to uninstall
+     * @throws ClientException if one of the requests failed
+     * @throws IOException if the files cannot be read from disk
      */
-    public void uninstallBundles(List<File> toUninstall) throws ClientException, IOException, InterruptedException {
+    public void uninstallBundles(List<File> toUninstall) throws ClientException, IOException {
         for(File f : toUninstall) {
             final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
@@ -137,11 +140,12 @@ public class BundlesInstaller {
 
     /**
      * Wait for all bundles specified in symbolicNames list to be installed in the OSGi web console.
+     * @deprecated use {@link #waitBundlesInstalled(List, long)}
      * @param symbolicNames the list of names for the bundles
      * @param timeoutSeconds how many seconds to wait
-     * @return
-     * @throws Exception
+     * @return true if all the bundles were installed
      */
+    @Deprecated
     public boolean waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
         log.info("Checking that the following bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
         for (String symbolicName : symbolicNames) {
@@ -152,66 +156,50 @@ public class BundlesInstaller {
     }
 
     /**
-     * Start all the bundles in a {{List}}
-     * @param symbolicNames the list of bundles to start
-     * @param timeoutSeconds number of seconds until it times out
-     * @throws ClientException
-     * @throws InterruptedException
+     * Wait for multiple bundles to be installed in the OSGi web console.
+     * @param symbolicNames the list bundles to be checked
+     * @param timeout max total time to wait for all bundles, in ms, before throwing {@code TimeoutException}
+     * @throws TimeoutException if the timeout was reached before all the bundles were installed
+     * @throws InterruptedException to mark this operation as "waiting", callers should rethrow it
      */
-    public void startAllBundles(final List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
-        log.info("Starting bundles (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
-        class StartAllBundlesPoller extends AbstractPoller {
-            private ClientException exception;
-            public StartAllBundlesPoller(List<String> symbolicNames, long waitInterval, long waitCount) {
-                super(waitInterval, waitCount);
-            }
+    public void waitBundlesInstalled(List<String> symbolicNames, long timeout)
+            throws InterruptedException, TimeoutException {
+        log.info("Checking that the following bundles are installed (timeout {} ms): {}", timeout, symbolicNames);
+        long start = System.currentTimeMillis();
+        for (String symbolicName : symbolicNames) {
+            osgiConsoleClient.waitBundleInstalled(symbolicName, timeout, 500);
 
-            @Override
-            public boolean call() {
-                for (String bundle : symbolicNames) {
-                    final String state;
-                    try {
-                        state = osgiConsoleClient.getBundleState(bundle);
-                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
-                            osgiConsoleClient.startBundle(bundle);
-                        }
-                    } catch (ClientException e) {
-                        this.exception = e;
-                        return false;
-                    }
-                }
-                return true;
+            if (System.currentTimeMillis() > start + timeout) {
+                throw new TimeoutException("Waiting for bundles did not finish in " + timeout + " ms.");
             }
+        }
+    }
 
+    /**
+     * Start all the bundles in a {{List}}
+     * @param symbolicNames the list of bundles to start
+     * @param timeout total max time to wait for all the bundles, in ms
+     * @throws TimeoutException if the timeout is reached before all the bundles are started
+     * @throws InterruptedException to mark this operation as "waiting", callers should rethrow it
+     */
+    public void startAllBundles(final List<String> symbolicNames, int timeout) throws InterruptedException, TimeoutException {
+        log.info("Starting bundles (timeout {} seconds): {}", timeout, symbolicNames);
+
+        Polling p = new Polling() {
             @Override
-            public boolean condition() {
+            public Boolean call() throws Exception {
+                boolean allActive = true;
                 for (String bundle : symbolicNames) {
-                    final String state;
-                    try {
-                        state = osgiConsoleClient.getBundleState(bundle);
-                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
-                            return false;
-                        }
-                    } catch (ClientException e) {
-                        this.exception = e;
-                        return false;
+                    String state = osgiConsoleClient.getBundleState(bundle);
+                    if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                        osgiConsoleClient.startBundle(bundle);
+                        allActive = false;
                     }
                 }
-                return true;
+                return allActive;
             }
+        };
 
-            public ClientException getException() {
-                return exception;
-            }
-        }
-        StartAllBundlesPoller poller = new StartAllBundlesPoller(symbolicNames, 1000, timeoutSeconds);
-        if (!poller.callUntilCondition()) {
-            throw new ClientException("Some bundles did not start or timed out", poller.getException());
-        }
-
+        p.poll(timeout, 500);
     }
-
-
-
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 08cd742..8dd8ca2 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -24,14 +24,14 @@ import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.testing.clients.ClientException;
-import org.apache.sling.testing.clients.SlingHttpResponse;
-import org.apache.sling.testing.clients.util.JsonUtils;
-import org.apache.sling.testing.clients.util.poller.AbstractPoller;
 import org.apache.sling.testing.clients.SlingClient;
 import org.apache.sling.testing.clients.SlingClientConfig;
+import org.apache.sling.testing.clients.SlingHttpResponse;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
+import org.apache.sling.testing.clients.util.JsonUtils;
 import org.apache.sling.testing.clients.util.poller.PathPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.codehaus.jackson.JsonNode;
 import org.osgi.framework.Constants;
 import org.slf4j.Logger;
@@ -41,7 +41,12 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URI;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 
@@ -204,11 +209,12 @@ public class OsgiConsoleClient extends SlingClient {
         }
         return props;
     }
-
     /**
      * Returns a map of all properties set for the config referenced by the PID, where the map keys
      * are the property names. The method waits until the configuration has been set.
      *
+     * @deprecated use {@link #waitGetConfiguration(long, String, int...)}
+     *
      * @param waitCount The number of maximum wait intervals of 500ms.
      *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
      * @param pid pid
@@ -217,11 +223,36 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the response status does not match any of the expectedStatus
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public Map<String, Object> getConfigurationWithWait(long waitCount, String pid, int... expectedStatus)
             throws ClientException, InterruptedException {
-        ConfigurationPoller poller = new ConfigurationPoller(500L, waitCount, pid, expectedStatus);
-        if (!poller.callUntilCondition())
-            return getConfiguration(pid, expectedStatus);
+        ConfigurationPoller poller = new ConfigurationPoller(pid, expectedStatus);
+        try {
+            poller.poll(500L * waitCount, 500);
+        } catch (TimeoutException e) {
+            throw new ClientException("Cannot retrieve configuration.", e);
+        }
+        return poller.getConfig();
+    }
+
+    /**
+     * Returns a map of all properties set for the config referenced by the PID, where the map keys
+     * are the property names. The method waits until the configuration has been set.
+     *
+     * @param timeout Maximum time to wait for the configuration to be available, in ms.
+     * @param pid service pid
+     * @param expectedStatus expected response status
+     * @return the config properties
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     * @throws TimeoutException if the timeout was reached
+     */
+    public Map<String, Object> waitGetConfiguration(long timeout, String pid, int... expectedStatus)
+            throws ClientException, InterruptedException, TimeoutException {
+
+        ConfigurationPoller poller = new ConfigurationPoller(pid, expectedStatus);
+        poller.poll(timeout, 500);
+
         return poller.getConfig();
     }
 
@@ -277,6 +308,8 @@ public class OsgiConsoleClient extends SlingClient {
      * Sets properties of a config referenced by its PID. the properties to be edited are passed as
      * a map of property (name,value) pairs. The method waits until the configuration has been set.
      *
+     * @deprecated use {@link #waitEditConfiguration(long, String, String, Map, int...)}
+     *
      * @param waitCount The number of maximum wait intervals of 500ms.
      *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
      * @param PID Persistent identity string
@@ -287,6 +320,7 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the response status does not match any of the expectedStatus
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public String editConfigurationWithWait(int waitCount, String PID, String factoryPID, Map<String, Object> configProperties,
                                             int... expectedStatus) throws ClientException, InterruptedException {
         String pid = editConfiguration(PID, factoryPID, configProperties, expectedStatus);
@@ -295,6 +329,28 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Sets properties of a config referenced by its PID. the properties to be edited are passed as
+     * a map of property (name,value) pairs. The method waits until the configuration has been set.
+     *
+     * @param timeout Max time to wait for the configuration to be set, in ms
+     * @param PID Persistent identity string
+     * @param factoryPID Factory persistent identity string or {@code null}
+     * @param configProperties map of properties
+     * @param expectedStatus expected response status
+     * @return the pid
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     * @throws TimeoutException if the timeout was reached
+     */
+    public String waitEditConfiguration(long timeout, String PID, String factoryPID, Map<String, Object> configProperties,
+                                        int... expectedStatus)
+            throws ClientException, InterruptedException, TimeoutException {
+        String pid = editConfiguration(PID, factoryPID, configProperties, expectedStatus);
+        waitGetConfiguration(timeout, pid);
+        return pid;
+    }
+
+    /**
      * Delete the config referenced by the PID
      *
      * @param pid pid
@@ -319,7 +375,7 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Uninstall a bundle
-     * @param symbolicName
+     * @param symbolicName bundle symbolic name
      * @return the sling response
      * @throws ClientException
      */
@@ -344,11 +400,11 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Install a bundle using the Felix webconsole HTTP interface, with a specific start level
-     * @param f
-     * @param startBundle
-     * @param startLevel
+     * @param f bundle file
+     * @param startBundle whether to start or just install the bundle
+     * @param startLevel start level
      * @return the sling response
-     * @throws ClientException
+     * @throws ClientException if the request failed
      */
     public SlingHttpResponse installBundle(File f, boolean startBundle, int startLevel) throws ClientException {
         // Setup request for Felix Webconsole bundle install
@@ -370,7 +426,24 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Check that specified bundle is installed and retries every {{waitTime}} milliseconds, until the
+     * bundle is installed or the number of retries was reached
+     * @deprecated does not respect polling practices; use {@link #waitBundleInstalled(String, long, long)} instead
+     * @param symbolicName the name of the bundle
+     * @param waitTime How many milliseconds to wait between retries
+     * @param retries the number of retries
+     * @return true if the bundle was installed until the retries stop, false otherwise
+     * @throws InterruptedException
+     */
+    @Deprecated
+    public boolean checkBundleInstalled(String symbolicName, int waitTime, int retries) throws InterruptedException {
+        final String path = getBundlePath(symbolicName, ".json");
+        return new PathPoller(this, path, waitTime, retries).callAndWait();
+    }
+
+    /**
      * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * @deprecated {@link #waitInstallBundle(File, boolean, int, long, long)}
      * @param f the bundle file
      * @param startBundle whether to start the bundle or not
      * @param startLevel the start level of the bundle. negative values mean default start level
@@ -379,6 +452,7 @@ public class OsgiConsoleClient extends SlingClient {
      * @return true if the bundle was successfully installed, false otherwise
      * @throws ClientException
      */
+    @Deprecated
     public boolean installBundleWithRetry(File f, boolean startBundle, int startLevel, int waitTime, int retries)
             throws ClientException, InterruptedException {
         installBundle(f, startBundle, startLevel);
@@ -390,24 +464,51 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Check that specified bundle is installed and retries every {{waitTime}} milliseconds, until the
-     * bundle is installed or the number of retries was reached
-     * @param symbolicName the name of the bundle
-     * @param waitTime How many milliseconds to wait between retries
-     * @param retries the number of retries
-     * @return true if the bundle was installed until the retries stop, false otherwise
+     * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * @param f the bundle file
+     * @param startBundle whether to start the bundle or not
+     * @param startLevel the start level of the bundle. negative values mean default start level
+     * @param timeout how much to wait for the bundle to be installed before throwing a {@code TimeoutException}
+     * @param delay time to wait between checks of the state
+     * @throws ClientException
+     * @throws TimeoutException if the bundle did not install before timeout was reached
      * @throws InterruptedException
      */
-    public boolean checkBundleInstalled(String symbolicName, int waitTime, int retries) throws InterruptedException {
-        final String path = getBundlePath(symbolicName, ".json");
-        return new PathPoller(this, path, waitTime, retries).callAndWait();
+    public void waitInstallBundle(File f, boolean startBundle, int startLevel, long timeout, long delay)
+            throws ClientException, InterruptedException, TimeoutException {
+
+        installBundle(f, startBundle, startLevel);
+        try {
+            waitBundleInstalled(getBundleSymbolicName(f), timeout, delay);
+        } catch (IOException e) {
+            throw new ClientException("Cannot get bundle symbolic name", e);
+        }
+    }
+
+    public void waitBundleInstalled(final String symbolicName, final long timeout, final long delay)
+            throws TimeoutException, InterruptedException {
+
+        final String path = getBundlePath(symbolicName);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                return exists(path);
+            }
+
+            @Override
+            protected String message() {
+                return "Bundle " + symbolicName + " did not install in %1$ ms";
+            }
+        };
+
+        p.poll(timeout, delay);
     }
 
     /**
      * Get the id of the bundle
-     * @param symbolicName
-     * @return
-     * @throws Exception
+     * @param symbolicName bundle symbolic name
+     * @return the id
+     * @throws ClientException if the id cannot be retrieved
      */
     public long getBundleId(String symbolicName) throws ClientException {
         final JSONObject bundle = getBundleData(symbolicName);
@@ -420,8 +521,8 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Get the version of the bundle
-     * @param symbolicName
-     * @return
+     * @param symbolicName bundle symbolic name
+     * @return bundle version
      * @throws ClientException
      */
     public String getBundleVersion(String symbolicName) throws ClientException {
@@ -435,9 +536,9 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Get the state of the bundle
-     * @param symbolicName
-     * @return
-     * @throws Exception
+     * @param symbolicName bundle symbolic name
+     * @return the state of the bundle
+     * @throws ClientException if the state cannot be retrieved
      */
     public String getBundleState(String symbolicName) throws ClientException {
         final JSONObject bundle = getBundleData(symbolicName);
@@ -460,13 +561,16 @@ public class OsgiConsoleClient extends SlingClient {
         this.doPost(path, FormEntityBuilder.create().addParameter("action", "start").build(), SC_OK);
     }
 
+
     /**
      * Starts a bundle and waits for it to be started
+     * @deprecated use {@link #waitStartBundle(String, long, long)}
      * @param symbolicName the name of the bundle
      * @param waitTime How many milliseconds to wait between retries
      * @param retries the number of retries
      * @throws ClientException, InterruptedException
      */
+    @Deprecated
     public void startBundlewithWait(String symbolicName, int waitTime, int retries)
             throws ClientException, InterruptedException {
         // start a bundle
@@ -476,6 +580,20 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Starts a bundle and waits for it to be started
+     * @param symbolicName the name of the bundle
+     * @param timeout max time to wait for the bundle to start, in ms
+     * @param delay time to wait between status checks, in ms
+     * @throws ClientException, InterruptedException, TimeoutException
+     */
+    public void waitStartBundle(String symbolicName, long timeout, long delay)
+            throws ClientException, InterruptedException, TimeoutException {
+        startBundle(symbolicName);
+        // FIXME this should wait for the started state
+        waitBundleInstalled(symbolicName, timeout, delay);
+    }
+
+    /**
      * Calls PackageAdmin.refreshPackages to force re-wiring of all the bundles.
      * @throws ClientException
      */
@@ -499,12 +617,25 @@ public class OsgiConsoleClient extends SlingClient {
         return URL_BUNDLES + "/" + symbolicName;
     }
 
+    /**
+     * Returns a data structure like:
+     *
+     * {
+     *   "status" : "Bundle information: 173 bundles in total - all 173 bundles active.",
+     *   "s" : [173,171,2,0,0],
+     *   "data": [{
+     *     "id":0,
+     *     "name":"System Bundle",
+     *     "fragment":false,
+     *     "stateRaw":32,
+     *     "state":"Active",
+     *     "version":"3.0.7",
+     *     "symbolicName":"org.apache.felix.framework",
+     *     "category":""
+     *   }]
+     * }
+     */
     private JSONObject getBundleData(String symbolicName) throws ClientException {
-        // This returns a data structure like
-        // {"status":"Bundle information: 173 bundles in total - all 173 bundles active.","s":[173,171,2,0,0],"data":
-        //  [
-        //      {"id":0,"name":"System Bundle","fragment":false,"stateRaw":32,"state":"Active","version":"3.0.7","symbolicName":"org.apache.felix.framework","category":""},
-        //  ]}
         final String path = getBundlePath(symbolicName, ".json");
         final String content = this.doGet(path, SC_OK).getContent();
 
@@ -536,9 +667,9 @@ public class OsgiConsoleClient extends SlingClient {
     //
 
     /**
-     * Get the symbolic name from a bundle file
-     * @param bundleFile
-     * @return
+     * Get the symbolic name from a bundle file by looking at the manifest
+     * @param bundleFile bundle file
+     * @return the name extracted from the manifest
      * @throws IOException
      */
     public static String getBundleSymbolicName(File bundleFile) throws IOException {
@@ -557,9 +688,9 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Get the version form a bundle file
-     * @param bundleFile
-     * @return
+     * Get the version form a bundle file by looking at the manifest
+     * @param bundleFile bundle file
+     * @return the version
      * @throws IOException
      */
     public static String getBundleVersionFromFile(File bundleFile) throws IOException {
@@ -578,32 +709,24 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
 
-    class ConfigurationPoller extends AbstractPoller {
+    class ConfigurationPoller extends Polling {
 
         private final String pid;
-        int[] expectedStatus;
-        public Map<String, Object> config;
+        private final int[] expectedStatus;
+        private Map<String, Object> config;
+
+        public ConfigurationPoller(String pid, int... expectedStatus) {
+            super();
 
-        public ConfigurationPoller(long waitInterval, long waitCount, String pid, int... expectedStatus) {
-            super(waitInterval, waitCount);
             this.pid = pid;
-            this.config = null;
             this.expectedStatus = expectedStatus;
+            this.config = null;
         }
 
         @Override
-        public boolean call() {
-            try {
-                config = getConfiguration(pid, expectedStatus);
-            } catch (ClientException e) {
-                LOG.warn("Couldn't get config " + pid, e);
-            }
-            return true;
-        }
-
-        @Override
-        public boolean condition() {
-            return null != config;
+        public Boolean call() throws Exception {
+            config = getConfiguration(pid, expectedStatus);
+            return config != null;
         }
 
         public Map<String, Object> getConfig() {
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
index 41ea39c..9f9c4bd 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
@@ -24,23 +24,26 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 
 /**
  * <p>Allows saving and restoring the OSGiConfig to be used before and after altering OSGi configurations for tests</p>
  * <p>See {@link InstanceConfig}</p>
  */
 public class OsgiInstanceConfig implements InstanceConfig {
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
 
     /**
-     * Number of retries for retrieving the current osgi config for save() and restore()
+     * Time im ms to wait for retrieving the current osgi config for save() and restore()
      */
-    protected int waitCount = 20;
+    private static final long WAIT_TIMEOUT = 20000;  // in ms
 
-    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
     private final OsgiConsoleClient osgiClient;
     private final String configPID;
     private Map<String, Object> config;
 
+    @Deprecated
+    protected int waitCount = 20;
 
     /**
      *
@@ -50,7 +53,8 @@ public class OsgiInstanceConfig implements InstanceConfig {
      * @throws ClientException if the client cannot be initialized
      * @throws InstanceConfigException if the config cannot be saved
      */
-    public <T extends SlingClient> OsgiInstanceConfig(T client, String configPID) throws ClientException, InstanceConfigException {
+    public <T extends SlingClient> OsgiInstanceConfig(T client, String configPID)
+            throws ClientException, InstanceConfigException, InterruptedException {
         this.osgiClient = client.adaptTo(OsgiConsoleClient.class);
         this.configPID = configPID;
 
@@ -63,14 +67,14 @@ public class OsgiInstanceConfig implements InstanceConfig {
      *
      * @throws InstanceConfigException if the config cannot be saved
      */
-    public InstanceConfig save() throws InstanceConfigException {
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException {
         try {
-            this.config = osgiClient.getConfigurationWithWait(waitCount, this.configPID);
+            this.config = osgiClient.waitGetConfiguration(WAIT_TIMEOUT, this.configPID);
             LOG.info("Saved OSGi config for {}. It is currently this: {}", this.configPID, this.config);
         } catch (ClientException e) {
             throw new InstanceConfigException("Error getting config", e);
-        } catch (InterruptedException e) {
-            throw new InstanceConfigException("Saving configuration was interrupted ", e);
+        } catch (TimeoutException e) {
+            throw new InstanceConfigException("Timeout of " + WAIT_TIMEOUT + " ms was reached while waiting for the configuration", e);
         }
         return this;
     }
@@ -80,14 +84,14 @@ public class OsgiInstanceConfig implements InstanceConfig {
      *
      * @throws InstanceConfigException if the config cannot be restored
      */
-    public InstanceConfig restore() throws InstanceConfigException {
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException {
         try {
-            osgiClient.editConfigurationWithWait(waitCount, this.configPID, null, config);
+            osgiClient.waitEditConfiguration(WAIT_TIMEOUT, this.configPID, null, config);
             LOG.info("restored OSGi config for {}. It is now this: {}", this.configPID, this.config);
         } catch (ClientException e) {
             throw new InstanceConfigException("Could not edit OSGi configuration", e);
-        } catch (InterruptedException e) {
-            throw new InstanceConfigException("Restoring configuration was interrupted", e);
+        } catch (TimeoutException e) {
+            throw new InstanceConfigException("Timeout of " + WAIT_TIMEOUT + " ms was reached while waiting for the configuration", e);
         }
         return this;
     }
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 119cb90..83ff1ab 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,7 +19,7 @@
 /**
  * OSGI testing tools.
  */
-@Version("1.1.0")
+@Version("1.2.0")
 package org.apache.sling.testing.clients.osgi;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
index c7b45c6..991d7e5 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.2.0")
+@Version("1.3.0")
 package org.apache.sling.testing.clients;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
index 4964300..fa6f99a 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
@@ -23,7 +23,11 @@ import org.slf4j.LoggerFactory;
  *  factor. Useful to cope with slower integration testing systems:
  *  use timeout constants in your code that work for usual development
  *  systems, and set a multiplier when running on a slower system.
+ *
+ *  @deprecated duplicate of {@link org.apache.sling.testing.timeouts.TimeoutsProvider}. This will be removed in the future, so switch to
+ *  the other one instead
  */
+@Deprecated
 public class TimeoutsProvider {
     private static final Logger log = LoggerFactory.getLogger(TimeoutsProvider.class);
     public static final String PROP_TIMEOUT_MULTIPLIER = "sling.testing.timeout.multiplier";
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
index 8af55ac..b545a7b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
@@ -28,7 +28,7 @@ public interface InstanceConfig {
      * @return this
      * @throws InstanceConfigException if saving the configuration fails
      */
-    public InstanceConfig save() throws InstanceConfigException;
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException;
 
     /**
      * Restores the saved status of the configuration
@@ -36,5 +36,5 @@ public interface InstanceConfig {
      * @return this
      * @throws InstanceConfigException if restoring the configuration fails
      */
-    public InstanceConfig restore() throws InstanceConfigException;
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException;
 }
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
index 4bae77f..7a82d4b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
@@ -103,7 +103,7 @@ public class InstanceConfigCacheImpl implements InstanceConfigCache {
 
 
     @Override
-    public InstanceConfig save() throws InstanceConfigException {
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException {
         for (InstanceConfig ic : configs) {
             ic.save();
         }
@@ -111,7 +111,7 @@ public class InstanceConfigCacheImpl implements InstanceConfigCache {
     }
 
     @Override
-    public InstanceConfig restore() throws InstanceConfigException {
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException {
         for (InstanceConfig ic : configs) {
             ic.restore();
         }
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
index 57c34c3..87b42c6 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
@@ -16,6 +16,11 @@
  */
 package org.apache.sling.testing.clients.util.poller;
 
+/**
+ * @deprecated use {@link Polling} instead.
+ * @see Polling for a better way to implement polling
+ */
+@Deprecated
 public abstract class AbstractPoller  implements Poller {
 
     private final long waitInterval;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
index 61a1ee1..cdbbf93 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Allows polling for a resource
  */
+@Deprecated
 public class PathPoller extends AbstractPoller {
     private static final Logger LOG = LoggerFactory.getLogger(PathPoller.class);
     private final AbstractSlingClient client;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
index e772edb..7ac2500 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
@@ -19,7 +19,11 @@ package org.apache.sling.testing.clients.util.poller;
 /**
  * Abstract Poller interface.
  * Provides simple methods to implement custom pollers
+ *
+ * @deprecated use {@link Polling} instead.
+ * @see Polling for a better way to implement polling
  */
+@Deprecated
 public interface Poller {
     boolean call();
     boolean condition();
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
index 4ff5bb0..4d723bb 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.testing.clients.util.poller;
 
 import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 13/37: @releng changed package versions in org.apache.sling.testing.clients

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 16afc27f4f5057b726b4212c641cce7badcd7a89
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Mon Sep 19 14:07:59 2016 +0000

    @releng changed package versions in org.apache.sling.testing.clients
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1761443 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 9a75bf1..a77f01f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
         <relativePath />
     </parent>
 
-        <artifactId>org.apache.sling.testing.clients</artifactId>
+    <artifactId>org.apache.sling.testing.clients</artifactId>
     <version>0.1.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 01/37: SLING-5703 - new http/clients module, extracted and enhanced from testing/tools. Contributed by Andrei Dulvac, thanks!

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit b29fdd63e70afb0732c7851fca882143844f5e82
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Fri Apr 29 14:03:32 2016 +0000

    SLING-5703 - new http/clients module, extracted and enhanced from testing/tools. Contributed by Andrei Dulvac, thanks!
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1741632 13f79535-47bb-0310-9956-ffa450edef68
---
 README.md                                          | 211 ++++++
 pom.xml                                            | 148 +++++
 .../sling/testing/clients/AbstractSlingClient.java | 715 +++++++++++++++++++++
 .../sling/testing/clients/ClientException.java     |  61 ++
 .../apache/sling/testing/clients/Constants.java    |  55 ++
 .../apache/sling/testing/clients/SlingClient.java  | 658 +++++++++++++++++++
 .../sling/testing/clients/SlingClientConfig.java   | 227 +++++++
 .../sling/testing/clients/SlingHttpResponse.java   | 396 ++++++++++++
 .../clients/instance/InstanceConfiguration.java    |  41 ++
 .../testing/clients/instance/InstanceSetup.java    |  88 +++
 .../interceptors/DelayRequestInterceptor.java      |  47 ++
 .../clients/interceptors/StickyCookieHolder.java   |  40 ++
 .../interceptors/StickyCookieInterceptor.java      |  61 ++
 .../clients/interceptors/StickyCookieSpec.java     |  47 ++
 .../interceptors/TestDescriptionHolder.java        |  49 ++
 .../interceptors/TestDescriptionInterceptor.java   |  42 ++
 .../testing/clients/interceptors/package-info.java |  24 +
 .../apache/sling/testing/clients/osgi/Bundle.java  |  55 ++
 .../sling/testing/clients/osgi/BundleInfo.java     | 124 ++++
 .../sling/testing/clients/osgi/BundlesInfo.java    | 144 +++++
 .../testing/clients/osgi/BundlesInstaller.java     | 186 ++++++
 .../sling/testing/clients/osgi/Component.java      |  51 ++
 .../sling/testing/clients/osgi/ComponentInfo.java  |  69 ++
 .../sling/testing/clients/osgi/ComponentsInfo.java |  95 +++
 .../testing/clients/osgi/OsgiConsoleClient.java    | 324 ++++++++++
 .../testing/clients/osgi/OsgiInstanceConfig.java   |  94 +++
 .../testing/clients/osgi/WebconsoleClient.java     | 190 ++++++
 .../sling/testing/clients/osgi/package-info.java   |  23 +
 .../apache/sling/testing/clients/package-info.java |  24 +
 .../testing/clients/util/FormEntityBuilder.java    |  81 +++
 .../sling/testing/clients/util/HttpUtils.java      | 183 ++++++
 .../clients/util/InputStreamBodyWithLength.java    |  69 ++
 .../sling/testing/clients/util/JsonUtils.java      |  44 ++
 .../sling/testing/clients/util/PortAllocator.java  |  58 ++
 .../sling/testing/clients/util/ResourceUtil.java   |  66 ++
 .../sling/testing/clients/util/SlingParameter.java | 108 ++++
 .../testing/clients/util/URLParameterBuilder.java  |  79 +++
 .../sling/testing/clients/util/UniquePaths.java    |  67 ++
 .../sling/testing/clients/util/XSSUtils.java       | 137 ++++
 .../clients/util/config/InstanceConfig.java        |  40 ++
 .../clients/util/config/InstanceConfigCache.java   |  25 +
 .../util/config/InstanceConfigException.java       |  39 ++
 .../util/config/impl/EmptyInstanceConfig.java      |  36 ++
 .../util/config/impl/InstanceConfigCacheImpl.java  | 120 ++++
 .../testing/clients/util/config/package-info.java  |  24 +
 .../clients/util/poller/AbstractPoller.java        |  69 ++
 .../sling/testing/clients/util/poller/Poller.java  |  28 +
 .../sling/testing/timeouts/TimeoutsProvider.java   |  77 +++
 .../testing/AbstractSlingClientGetPathTest.java    | 162 +++++
 .../AbstractSlingClientGetServerUrlTest.java       |  56 ++
 .../testing/AbstractSlingClientGetUrlTest.java     | 122 ++++
 .../sling/testing/DelayRequestInterceptorTest.java |  34 +
 .../apache/sling/testing/util/UniquePathsTest.java |  76 +++
 .../testing/util/poller/AbstractPollerTest.java    | 107 +++
 54 files changed, 6196 insertions(+)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..adce441
--- /dev/null
+++ b/README.md
@@ -0,0 +1,211 @@
+# Sling Http Clients
+
+`SlingClient` is a specialized
+[`HttpClient`](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/HttpClient.html)
+that provides additional functionalities specific to Sling. It is designed to be easy to use out of the box, but also fully customizable.
+This library comes with a bunch of other specialized clients (built on top of `SlingClient`) that are ready to use.
+
+## <a name="architecture"></a> Architecture
+
+`SlingClient`  implements the `HttpClient` interface, but [deletegates](https://en.wikipedia.org/wiki/Delegation_pattern)
+this functionality to a `private final CloseableHttpClient http` field.
+The config is stored in a `private final SlingClientConfig config` field which is immutable and may be shared across multiple clients
+(more about it in the [How to configure a SlingClient](#config) section).
+These two objects define the state of the client and are built to make the client thread safe.
+
+`SlingClient` is designed in two layers:
+* The base `class AbstractSlingClient implements HttpClient` provides an overlay of basic http methods such as `doGet()`,
+  `doPost()` & similar. These are meant to be full replacements of the ones in `HttpClient` for Sling specific needs,
+  and they add specific customizations. One particularity is that they all return `SlingHttpResponse`, an augmented `HttpResponse`.
+
+  Still, all the methods from `HttpClient` are exposed (through inheritance and delegation) in case one needs the raw functionality.
+  Some useful methods to manipulate Sling paths and URLs have also been added (`getUrl()`, `getPath()`).
+
+  This class encapsulates the mechanisms for extensibility (immutable config field, delegate client field, package private constructor,
+  `adaptTo()`), but it is defined as abstract and should never be used directly.
+
+* The main `class SlingClient extends AbstractSlingClient` is the one that adds Sling specific methods (`createNode()`,
+  `deletePath()` etc.). It has no fields, but makes use of everything that was defined in the super class.
+  Another main functionality defined in `SlingClient` are the mechanisms to instantiate a SlingClient (and any other sub-class):
+
+  * constructor: `public SlingClient(URI url, String user, String password) throws ClientException`
+
+  * builder: `public final static class Builder extends InternalBuilder<SlingClient>` (more in [How to write a `Builder`](#builder))
+
+Any client you write should extend `SlingClient` (more in [How to extend `SlingClient`](#extend))
+
+## <a name="instantiate"></a> How to instantiate `SlingClient`
+There are several ways to obtain a SlingClient (and sub-client) instance, depending on the resources available:
+
+* constructor `SlingClient(URI url, String user, String password)` - handy for obtaining a simple client from the url:
+  ```java
+  SlingClient c = new SlingClient(URI.create("localhost:8080"), "admin", "admin");
+  ```
+
+* builder `class Builder<T extends Builder> extends HttpClientBuilder` - this allows for more complex clients to be created, e.g.
+  with different authentication mechanism, or additional interceptors:
+  ```java
+  SlingClient c = SlingClient.Builder.create("localhost:8080", "admin", "admin").build();
+  ```
+  This gives the possibility to customize the HttpClient (e.g. add interceptors, change auth method) before constructing it.
+
+* `public <T extends AbstractSlingClient> T adaptTo(Class<T> clientClass)` is the convenient method to obtain another specialized
+client form an existing one. The advantage is that the two will share the same configuration and http handler, so they will behave
+like two different "facets" of the same client (think about the analogy of a Web browser with multiple tabs).
+
+Although the constructor and the builder are very handy, the preferred way of working with clients is to obtain it using one of the
+Junit Rules provided (e.g. `ExistingQuickstart`) and then call `adaptTo()`.
+
+## <a name="config"></a> How to configure `SlingClient`
+All the configs specific to `SlingClient` are stored in `private final SlingClientConfig config` which contains fields such as
+`url`, `cookieStore` and authentication parameters. These fields can be set only indirectly, through constructor or Builder, and only
+before constructing the client. They cannot be changed, so if you need to change something, you must instantiate another client.
+
+`SlingClient` was designed to be immutable, so thread safe. You don't have to worry about synchronizing it when running tests in parallel.
+Also, the immutable config is the base for the `adaptTo()` mechanism, since the two clients can share the same config.
+
+## <a name="extend"></a> How to extend `SlingClient`
+The `SlingClient` was designed with extensibility in mind. That's why it provides only basic functionality, leaving other specialized
+clients to implement the rest. To create a new client class (let's call it `MyClient`), you need to:
+* extend SlingClient: `class MyClient extends SlingClient`
+* implement the two constructors:
+  * the one for simple uses:
+  ```java
+    public MyClient(URI serverUrl, String userName, String password) throws ClientException {
+      super(serverUrl, userName, password);
+    }
+    ```
+  * the one used by `adaptTo()` (so don't forget it!):
+  ```java
+    public MyClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+      super(http, config);
+    }
+    ```
+  * optionally create your `Builder`, but only if needed (more in [How to write a `Builder`](#builder))
+
+A good example of how `SlingClient` can be extended is `OsgiConsoleClient`. Note you can further extend the sub-clients in the same way.
+
+## <a name="builder"></a> How to write a `Builder`
+If you need to make your client customizable you will have to write your own Builder (you were no thinking to break the immutability
+by adding a setter, right?). Below is an example of how to create the Builder mechanism that you can take and adapt for your needs.
+In this case, we try to expose only one field `foo`, but it can be extended to any number of fields. Although it seems complicated,
+if you follow exactly the example, you cannot fail. Trying to simplify it will burn you (sooner or later), you have been warned!
+
+A short description of the Builder architecture would be: the `InternalBuilder` contains all the logic while staying extensible, while
+`Builder` takes all the credit by exposing the `build()` method. Yet, the `Builder` cannot be extended because all the sub-classes would
+return a `SlingClient` when calling `build()` (and not a subclass instance).
+
+```java
+@Immutable
+public class MyClient extends SlingClient {
+
+    private final String foo;
+
+    public MyClient(URI serverUrl, String user, String password) throws ClientException {
+        super(serverUrl, user, password);
+    }
+
+    /**
+     * Constructor used by Builders and adaptTo(). <b>Should never be called directly from the code.</b>
+     *
+     * @see AbstractSlingClient#AbstractSlingClient(CloseableHttpClient, SlingClientConfig)
+     */
+    public MyClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        super(http, config);
+    }
+
+    public static abstract class InternalBuilder<T extends MyClient> extends SlingClient.InternalBuilder<T> {
+        protected String foo;
+
+        protected InternalBuilder(URI url, String user, String password) {
+            super(url, user, password);
+        }
+
+        public InternalBuilder<T> withFoo(String foo) {
+          this.foo = foo;
+        }
+    }
+
+    public final static class Builder extends InternalBuilder<MyClient> {
+
+        private Builder(URI url, String user, String password) {
+            super(url, user, password);
+        }
+
+        @Override
+        public MyClient build() throws ClientException {
+            MyClient client = new MyClient(buildHttpClient(), buildSlingClientConfig());
+            client.foo = this.foo;
+            return client;
+        }
+
+        public static Builder create(URI url, String user, String password) {
+            return new Builder(url, user, password);
+        }
+    }
+}
+```
+
+## FAQ
+##### How can I change the server url of an existing client?
+You don't. As described in [How to configure a `SlingClient`](#config), you have to instantiate another client to change the config.
+
+##### How can I create a client for a server url with context path?
+The server `url` (passed in the constructor or builder) must contain all the elements, including protocol, hostname, port and eventually
+the context path, e.g.: `http://localhost:8080/mycontextpath/`.
+The url may (or may not) contain the trailing slash. Yet, the client will always store it with a trailing slash:
+```java
+SlingClient client = new SlingClient("http://localhost:4502/mycontextpath", "user", "pass");
+System.out.println(client.getUrl());
+// prints http://localhost:4502/mycontextpath/
+```
+
+##### How can I customize the underlying `HttpClient`?
+The `SlingClient.Builder` directly exposes the most useful methods from `HttpClientBuilder`, but not all of them.
+First, check if you can find it there. If you haven't found your specific method, then the `Builder` exposes an `HttpClientBuilder` through
+`public HttpClientBuilder httpClientBuilder()` which you can use to config it. Note that in this case you cannot chain the methods
+to build the client, so you will need to keep a reference to the `SlingClient.Builder`:
+```java
+SlingClient.Builder builder = SlingClient.Builder.create("http://localhost:8080", "user", "pass");
+HttpClientBuilder httpBuilder = builder.httpClientBuilder();
+httpBuilder.setProxy(myProxy);
+builder.setUser("another");
+SlingClient client = builder.build();
+```
+
+##### Why is the `Builder` pattern so complicated? Do I really need two classes?
+Don't try to get creative here. Respect the examples provided and don't take shortcuts, otherwise you will hurt yourself.
+
+We have tried different ways of designing the Builder. This is the best compromise between extensibility and simplicity. The
+`HttpClientBuilder` does not offer any extensibility support, so `SlingClient.Builder` does not extend it, it just uses it internally.
+Always remember that you don't need to create your Builder, unless you want to add custom fields to the client.
+
+##### Why I cannot use the entity's content InputStream?
+`SlingClient#doRequest()`, `SlingClient#doGet()`, `SlingClient#doPost()` & co. are all consuming the entity and caching it as
+String. This is by design, since there's a big risk to forget closing the connections and to run out of sockets quickly.
+If you need the response content as InputStream (e.g. for downloading a binary), you can use `doStreamGet()` or similar. These
+methods were written specially to not consume the entity so it's the caller's responsibility to close it when finished. Remember to use
+them with caution and only when needed.
+
+##### Can my client use another authentication method?
+The username and password required by the constructor and builder are there for convenience (since more than 90% of cases will use
+basic auth). But you can easily overwrite the `CredentialsProvider` in Builder so those will be ignored. Or do anything you want with
+that `HttpClientBuilder`...
+
+##### How can I obtain the context path?
+`client.getUrl().getPath()`
+
+##### How can I obtain the "relative" url (excluding hostname and port, but including context path)?
+`client.getUrl(path).getPath()`
+
+##### How can I remove the context path from a path?
+`client.getPath(path)`
+
+##### What if I pass an url or a path with or without context path to `getUrl()` or `getPath()`?
+We have tried to make these methods as robust as possible. Their job is very clear:
+* `getUrl(String path)` to transform a Sling path into a full url
+* `getPath(String url)` to transform a full url into a Sling path
+
+Any input that does not respect the contract might not work. Check `AbstractSlingClientGetPathTest` and `AbstractSlingClientGetUrlTest`
+for an extensive list of cases that we have considered when writing these methods.
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f0da527
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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>26</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.testing.clients</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Testing Clients</name>
+    <description>
+        Sling testing http clients and utils
+    </description>
+    
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+    </scm>
+    
+    <build>
+        <plugins>
+           <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+             <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.sling.testing.clients.*,
+                        </Export-Package>
+                        <Import-Package>
+                            org.apache.commons.exec.*; resolution:=optional,
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.tools</artifactId>
+            <version>1.0.12</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.httpcomponents</groupId>
+                    <artifactId>httpcore</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+       <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-exec</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+            <version>1.5.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+            <version>1.5.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+            <version>4.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.7.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>14.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.xss</artifactId>
+            <version>1.0.4</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java b/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
new file mode 100644
index 0000000..24d731f
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
@@ -0,0 +1,715 @@
+/*
+ * 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.testing.clients;
+
+import org.apache.http.*;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.client.*;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.HttpContext;
+import org.apache.sling.testing.clients.util.HttpUtils;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * The abstract base client for all implementing integration test clients.
+ */
+@Immutable
+public class AbstractSlingClient implements HttpClient {
+
+    private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
+
+    private final static URI slash = URI.create("/");
+
+    /**
+     * The clientId for the client, generated automatically during instantiation of client.
+     */
+    private final String clientId;
+
+    /**
+     * The HttpClient object to which http calls are delegated.
+     * It can be shared across multiple AbstractSlingClients (by using adaptTo())
+     */
+    private final CloseableHttpClient http;
+
+    /**
+     * A wrapper object containing the sling config for this client.
+     * It can be shared across multiple AbstractSlingClients (by using adaptTo())
+     */
+    private final SlingClientConfig config;
+
+    /**
+     * Constructor used by Builders and adaptTo(). <b>Should never be called directly from the code.</b>
+     *
+     * @param http http client to handle the delegated calls
+     * @param config immutable object holding the config
+     * @throws ClientException if the client could not be initialized
+     */
+    AbstractSlingClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        // Generate client ID
+        this.clientId = this.getClass() + "-" + UUID.randomUUID().toString();
+        this.http = http;
+        this.config = config;
+    }
+
+    /**
+     * Returns the unique id for this client, generated automatically during instantiation.<br>
+     *
+     * @return client's unique id
+     */
+    protected String getClientId() {
+        return clientId;
+    }
+
+    /**
+     * <p>Base HTTP URI of the server under test. It includes the context path, if present, and always ends with a slash</p>
+     * <p>Example: {@code http://localhost:8080/a/}</p>
+     *
+     * @return the server's URL
+     */
+    public URI getUrl() {
+        return config.getUrl();
+    }
+
+
+    /**
+     * Returns the name of the user that will be used to authenticate the requests (by basic auth, if not replaced).
+     *
+     * @return user's name
+     */
+    public String getUser() {
+        return config.getUser();
+    }
+
+    /**
+     * Returns the password of the user that will be used to authenticate the requests (by basic auth, if not replaced).
+     *
+     * @return user's password
+     */
+    public String getPassword() {
+        return config.getPassword();
+    }
+
+    /**
+     * <p>Gets the full URL for a given path.</p>
+     *
+     * <p>The input path is considered relative to server url path ("/" or context path), even though it starts with a slash.
+     * The path is relativized and appended to the {@code server url}.</p>
+     *
+     * <p>Note: in the case of a server url with context path - the input path should not contain the context path, otherwise
+     * it will be duplicated in the resulting url</p>
+     *
+     * @param path the relative path
+     * @return the absolute URI
+     * @throws IllegalArgumentException if path cannot be parsed into an URI
+     * @throws NullPointerException if path is null
+     */
+    public URI getUrl(String path) {
+        try {
+            URI pathUri = slash.relativize(new URI(path));
+            return getUrl().resolve(pathUri);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * Creates a full URL for a given path with additional parameters. Same as {@link #getUrl(String)}, but adds the parameters in the URI.
+     *
+     * @param path path relative to server url; can start with / but should not include the server context path
+     * @param parameters url parameters to be added to the url
+     * @return full url as URI
+     * @throws IllegalArgumentException if path or parameters cannot be parsed into an URI
+     * @throws NullPointerException if path is null
+     */
+    public URI getUrl(String path, List<NameValuePair> parameters) {
+        // add server url and path
+        URIBuilder uriBuilder = new URIBuilder(getUrl(path));
+        // add parameters
+        parameters = (parameters != null) ? parameters : new ArrayList<NameValuePair>(0);
+        uriBuilder.addParameters(parameters);
+
+        try {
+            return uriBuilder.build();
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * <p>Transforms an external {@code url} into a sling path, by subtracting the {@code server url} (incl. contextPath).
+     * The returned path will not contain the context path, so it can be used with {@link #getUrl(String)}</p>
+     *
+     * <p>The url can be absolute (incl. hostname) or relative to root (starts with "/").</p>
+     *
+     * <p>If the server url is not a prefix of the given url, it returns the given url</p>
+     *
+     * <p>If the url is just a path, it returns the path (with leading slash if not already present)</p>
+     *
+     * @param url full url
+     * @return sling path
+     */
+    public URI getPath(URI url) {
+        // special case for urls that are server urls, but without trailing slash
+        if (url.relativize(getUrl()).equals(URI.create(""))) {
+            return slash;
+        }
+
+        URI contextPath = URI.create(getUrl().getPath());
+        URI relativeUrl = contextPath.relativize(slash.resolve(url));
+
+        if (relativeUrl.relativize(contextPath).equals(URI.create(""))) {
+            return slash;
+        }
+
+        return slash.resolve(getUrl().relativize(relativeUrl));
+    }
+
+    /**
+     * Extracts the relative sling path (to server url) from an url. Identical to {@link AbstractSlingClient#getPath(URI)},
+     * except that it also parses the String int URI
+     *
+     * @param url string containing the full url
+     * @return relative path as URI
+     * @throws IllegalArgumentException if the parameter cannot be parsed
+     * @throws NullPointerException if url is null
+     */
+    public URI getPath(String url) {
+        try {
+            return getPath(new URI(url));
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * <p>Returns an instance of any class extending the AbstractSlingClient. The new client will
+     * use the the same {@link HttpClient} and {@link SlingClientConfig} </p>
+     *
+     * @param clientClass the type of client requested, identified by its Class
+     * @param <T>         any class extending the AbstractSlingClient
+     * @return instance of a class extending the AbstractSlingClient
+     * @throws ClientException if client can't be instantiated
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSlingClient> T adaptTo(Class<T> clientClass) throws ClientException {
+        T client;
+        try {
+            Constructor cons = clientClass.getConstructor(CloseableHttpClient.class, SlingClientConfig.class);
+            client = (T) cons.newInstance(this.http, this.config);
+        } catch (Exception e) {
+            throw new ClientException("Could not initialize client: '" + clientClass.getCanonicalName() + "'.", e);
+        }
+        return client;
+    }
+
+    /**
+     * Gets the value for {@code key} from the generic values
+     *
+     * @param key the key
+     * @return the value
+     */
+    public String getValue(String key) {
+        return this.config.getValues().get(key);
+    }
+
+    /**
+     * Adds the extra {@code key, value} to the generic values
+     *
+     * @param key the key for witch to add a value
+     * @param value the value
+     */
+    public void addValue(String key, String value) {
+        this.config.getValues().put(key, value);
+    }
+
+    /**
+     * Checks whether the handler has the given generic value
+     *
+     * @param key the key
+     * @return true if the value was found
+     */
+    public boolean hasValue(String key) {
+        return config.getValues().containsKey(key);
+    }
+
+    /**
+     * Returns the extra values map
+     *
+     * @return the map of values
+     */
+    public Map<String, String> getValues() {
+        return config.getValues();
+    }
+
+    /**
+     * @return the cookie store reference
+     */
+    public CookieStore getCookieStore() {
+        return config.getCookieStore();
+    }
+
+    /**
+     * @return the credentials provider
+     */
+    public CredentialsProvider getCredentialsProvider() {
+        return config.getCredsProvider();
+    }
+
+    //
+    // HTTP convenience methods
+    //
+
+    /**
+     * <p>Executes an HTTP request, WITHOUT consuming the entity in the response. The caller is responsible for consuming the entity or
+     * closing the response's InputStream in order to release the connection.
+     * Otherwise, the client might run out of connections and will block</p>
+     *
+     * <p><b>Use this with caution and only if necessary for streaming</b>, otherwise use the safe method
+     * {@link #doRequest(HttpUriRequest, List, int...)}</p>
+     *
+     * <p>Adds the headers and checks the response against expected status</p>
+     *
+     * @param request the request to be executed
+     * @param headers optional headers to be added to the request
+     * @param expectedStatus if passed, the response status is checked against it/them, and has to match at least one of them
+     * @return the response, with the entity not consumed
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doStreamRequest(HttpUriRequest request, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        // create context from config
+        HttpClientContext context = createHttpClientContextFromConfig();
+
+        // add headers
+        if (headers != null) {
+            request.setHeaders(headers.toArray(new Header[headers.size()]));
+        }
+
+        try {
+            log.debug("request {} {}", request.getMethod(), request.getURI());
+            SlingHttpResponse response = new SlingHttpResponse(this.execute(request, context));
+            log.debug("response {}", HttpUtils.getHttpStatus(response));
+            // Check the status and throw a ClientException if it doesn't match expectedStatus, but close the entity before
+            if (expectedStatus != null && expectedStatus.length > 0) {
+                try {
+                    HttpUtils.verifyHttpStatus(response, expectedStatus);
+                } catch (ClientException e) {
+                    // catch the exception to make sure we close the entity before re-throwing it
+                    response.close();
+                    throw e;
+                }
+            }
+
+            return response;
+        } catch (IOException e) {
+            throw new ClientException("Could not execute http request", e);
+        }
+    }
+
+    /**
+     * <p>Executes a raw HTTP request, WITHOUT consuming the entity in the response. The caller is responsible for consuming the entity or
+     * closing the response's InputStream in order to release the connection.
+     * Otherwise, the client might run out of connections and will block</p>
+     *
+     * <p><b>Use this with caution and only if necessary for custom methods or for paths that must not be encoded</b>,
+     * otherwise use the safe method {@link #doRequest(HttpUriRequest, List, int...)}</p>
+     *
+     * <p>It behaves as {@link #doStreamRequest(HttpUriRequest, List, int...)}, so the entity is not consumed.</p>
+     * <p>Adds the headers and checks the response against expected status</p>
+     *
+     * @param method the request to be executed
+     * @param uri the uri to be sent as it is (will not prepend the context path)
+     * @param headers optional headers to be added to the request
+     * @param expectedStatus if passed, the response status is checked against it/them, and has to match at least one of them
+     * @return the response, with the entity not consumed
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doRawRequest(String method, String uri, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        // create context from config
+        HttpClientContext context = createHttpClientContextFromConfig();
+
+        HttpHost host = new HttpHost(getUrl().getHost(), getUrl().getPort(), getUrl().getScheme());
+        HttpRequest request = new BasicHttpRequest(method, uri);
+
+        // add headers
+        if (headers != null) {
+            request.setHeaders(headers.toArray(new Header[headers.size()]));
+        }
+
+        try {
+            log.debug("request {} {}", method, uri);
+            SlingHttpResponse response = new SlingHttpResponse(this.execute(host, request, context));
+            log.debug("response {}", HttpUtils.getHttpStatus(response));
+            // Check the status and throw a ClientException if it doesn't match expectedStatus, but close the entity before
+            if (expectedStatus != null && expectedStatus.length > 0) {
+                try {
+                    HttpUtils.verifyHttpStatus(response, expectedStatus);
+                } catch (ClientException e) {
+                    // catch the exception to make sure we close the entity before re-throwing it
+                    response.close();
+                    throw e;
+                }
+            }
+
+            return response;
+        } catch (IOException e) {
+            throw new ClientException("Could not execute http request", e);
+        }
+    }
+
+    private HttpClientContext createHttpClientContextFromConfig() {
+        // create context from config
+        HttpClientContext context = HttpClientContext.create();
+
+        if (config.getCookieStore() != null) {
+            context.setCookieStore(config.getCookieStore());
+        }
+
+        if (config.getCredsProvider() != null) {
+            context.setCredentialsProvider(config.getCredsProvider());
+        }
+
+        if (config.getAuthCache() != null) {
+            context.setAuthCache(config.getAuthCache());
+        }
+
+        return context;
+    }
+
+    /**
+     * <p>Executes a GET request WITHOUT consuming the entity in the response. The caller is responsible to close the connection.
+     * Otherwise, the client might run out of connections and will block</p>
+     *
+     * <p><b>Use this with caution and only if necessary for streaming</b>, otherwise use the safe method
+     * {@link #doGet(String, List, List, int...)}</p>
+     *
+     * <p>Adds the given parameters and headers and checks the response against expected status</p>
+     * @param requestPath path relative to client url
+     * @param parameters optional url parameters to be added
+     * @param headers optional headers to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity not consumed
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doStreamGet(String requestPath, List<NameValuePair> parameters, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        // create full uri, including server url, given path and given parameters
+        URI uri = getUrl(requestPath, parameters);
+        // execute request
+        HttpUriRequest request = new HttpGet(uri);
+        return doStreamRequest(request, headers, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a POST request WITHOUT consuming the entity in the response. The caller is responsible to close the connection</p>
+     *
+     * <p><b>Use this with caution and only if necessary for streaming</b>, otherwise use the safe method
+     * {@link #doPost(String, HttpEntity, List, int...)}</p>
+     *
+     * <p>Adds the headers and checks the response against expected status</p>
+     * @param requestPath path relative to client url
+     * @param entity http entity to be sent by POST
+     * @param headers optional headers to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity not consumed
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doStreamPost(String requestPath, HttpEntity entity, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpEntityEnclosingRequestBase request = new HttpPost(getUrl(requestPath));
+        if (entity != null) {
+            request.setEntity(entity);
+        }
+        return doStreamRequest(request, headers, expectedStatus);
+    }
+
+    /**
+     * <p>Execute an HTTP request and consumes the entity in the response. The content is cached and can be retrieved using
+     * {@code response.getContent()}.
+     * This method is safe to use because it closes the entity so the caller has no responsibility.</p>
+     *
+     * <p>This means the response entity SHOULD NOT BE USED to read the content, e.g. {@code response.getEntity().getContent()}</p>
+     *
+     * @param request the request to be executed
+     * @param headers optional headers to be added to the request
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public  SlingHttpResponse doRequest(HttpUriRequest request, List<Header> headers, int... expectedStatus) throws ClientException {
+        SlingHttpResponse response = doStreamRequest(request, headers, expectedStatus);
+
+        // Consume entity and cache the content so the connection is closed
+        response.getContent();
+
+        return response;
+    }
+
+    /**
+     * <p>Executes a GET request and consumes the entity in the response (so the connection is closed immediately)
+     * The content is cached and can be retrieved using {@code response.getContent()}.</p>
+     *
+     * <p>Adds the passed parameters and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param parameters optional url parameters to be added
+     * @param headers optional headers to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed amd the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doGet(String requestPath, List<NameValuePair> parameters, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        SlingHttpResponse response = doStreamGet(requestPath, parameters, headers, expectedStatus);
+
+        // Consume entity and cache the content so the connection is closed
+        response.getContent();
+
+        return response;
+    }
+
+    /**
+     * <p>Executes a GET request and consumes the entity in the response (so the connection is closed immediately)
+     * The content is cached and can be retrieved using {@code response.getContent()}.</p>
+     *
+     * <p>Adds the passed parameters and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param parameters optional url parameters to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed amd the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doGet(String requestPath, List<NameValuePair> parameters, int... expectedStatus)
+            throws ClientException {
+        return doGet(requestPath, parameters, null, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a GET request and consumes the entity in the response (so the connection is closed immediately)
+     * The content is cached and can be retrieved using {@code response.getContent()}.</p>
+     *
+     * @param requestPath path relative to client url
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed amd the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doGet(String requestPath, int... expectedStatus)
+            throws ClientException {
+        return doGet(requestPath, null, null, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a HEAD request</p>
+     *
+     * <p>Adds the passed parameters and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param parameters optional url parameters to be added
+     * @param headers optional headers to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doHead(String requestPath, List<NameValuePair> parameters, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpUriRequest request = new HttpHead(getUrl(requestPath, parameters));
+        return doRequest(request, headers, expectedStatus);
+    }
+
+
+    /**
+     * <p>Executes a POST request and consumes the entity in the response. The content is cached and be retrieved by calling
+     * {@code response.getContent()}</p>
+     *
+     * <p>Adds the passed entity and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param entity the entity to be added to request
+     * @param headers optional headers to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doPost(String requestPath, HttpEntity entity, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpEntityEnclosingRequestBase request = new HttpPost(getUrl(requestPath));
+        if (entity != null) {
+            request.setEntity(entity);
+        }
+        return doRequest(request, headers, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a POST request and consumes the entity in the response. The content is cached and be retrieved by calling
+     * {@code response.getContent()}</p>
+     *
+     * <p>Adds the passed entity and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param entity the entity to be added to request
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doPost(String requestPath, HttpEntity entity, int... expectedStatus)
+            throws ClientException {
+        return doPost(requestPath, entity, null, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a PUT request and consumes the entity in the response. The content is cached and be retrieved by calling
+     * {@code response.getContent()}</p>
+     *
+     * <p>Adds the passed entity and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param entity the entity to be added to request
+     * @param headers optional url parameters to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doPut(String requestPath, HttpEntity entity, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpEntityEnclosingRequestBase request = new HttpPut(getUrl(requestPath));
+        if (entity != null) {
+            request.setEntity(entity);
+        }
+        return doRequest(request, headers, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a PATCH request and consumes the entity in the response. The content is cached and be retrieved by calling
+     * {@code response.getContent()}</p>
+     *
+     * <p>Adds the passed entity and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param entity the entity to be added to request
+     * @param headers optional url parameters to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doPatch(String requestPath, HttpEntity entity, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpEntityEnclosingRequestBase request = new HttpPatch(getUrl(requestPath));
+        if (entity != null) {
+            request.setEntity(entity);
+        }
+        return doRequest(request, headers, expectedStatus);
+    }
+
+    /**
+     * <p>Executes a DELETE request and consumes the entity in the response. The content is cached and be retrieved by calling
+     * {@code response.getContent()}</p>
+     *
+     * <p>Adds the passed parameters and headers and checks the expected status</p>
+     *
+     * @param requestPath path relative to client url
+     * @param parameters optional url parameters to be added
+     * @param headers optional url parameters to be added
+     * @param expectedStatus if passed, the response status will have to match one of them
+     * @return the response with the entity consumed and the content cached
+     * @throws ClientException if the request could not be executed
+     */
+    public SlingHttpResponse doDelete(String requestPath, List<NameValuePair> parameters, List<Header> headers, int... expectedStatus)
+            throws ClientException {
+        HttpUriRequest request = new HttpDelete(getUrl(requestPath, parameters));
+        return doRequest(request, headers, expectedStatus);
+    }
+
+
+    //
+    // HttpClient  base methods
+    //
+
+    @Deprecated
+    @SuppressWarnings("deprecation")
+    public org.apache.http.params.HttpParams getParams() {
+        return this.http.getParams();
+    }
+
+    @Deprecated
+    @SuppressWarnings("deprecation")
+    public org.apache.http.conn.ClientConnectionManager getConnectionManager() {
+        return this.http.getConnectionManager();
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
+        return this.http.execute(request);
+    }
+
+    // maybe throw UnsupportedMethodException
+    @SuppressWarnings("DuplicateThrows")
+    public CloseableHttpResponse execute(HttpUriRequest request, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(request, context);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public HttpResponse execute(HttpHost target, HttpRequest request)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(target, request);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public CloseableHttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(target, request, context);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(request, responseHandler);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(request, responseHandler, context);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(target, request, responseHandler);
+    }
+
+    @SuppressWarnings("DuplicateThrows")
+    public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
+            throws IOException, ClientProtocolException {
+        return this.http.execute(target, request, responseHandler, context);
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/ClientException.java b/src/main/java/org/apache/sling/testing/clients/ClientException.java
new file mode 100644
index 0000000..a598564
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/ClientException.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.testing.clients;
+
+/**
+ *
+ */
+public class ClientException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+    private int httpStatusCode = -1;
+
+    public ClientException(String message) {
+        this(message, null);
+    }
+
+    public ClientException(String message, Throwable throwable) {
+        this(message, -1, throwable);
+    }
+
+    public ClientException(String message, int htmlStatusCode, Throwable throwable) {
+        super(message, throwable);
+        this.httpStatusCode = htmlStatusCode;
+    }
+
+    /**
+     * @return the htmlStatusCode
+     */
+    public int getHttpStatusCode() {
+        return httpStatusCode;
+    }
+
+    /*
+      * (non-Javadoc)
+      *
+      * @see java.lang.Throwable#getMessage()
+      */
+    @Override
+    public String getMessage() {
+        String message = super.getMessage();
+        if (httpStatusCode > -1) {
+            message = message + "(return code=" + httpStatusCode + ")";
+        }
+        return message;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/Constants.java b/src/main/java/org/apache/sling/testing/clients/Constants.java
new file mode 100644
index 0000000..3e7ce66
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/Constants.java
@@ -0,0 +1,55 @@
+/*
+ * 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.testing.clients;
+
+public class Constants {
+
+    /**
+     * Prefix for IT-specific system properties
+     */
+    public static final String CONFIG_PROP_PREFIX = "sling.it.";
+    public static final String DEFAULT_URL = "http://localhost:8080/";
+    public static final String DEFAULT_USERNAME = "admin";
+    public static final String DEFAULT_PASSWORD = "admin";
+
+    // Custom delay for requests
+    private static long delay;
+    static {
+        try {
+            Constants.delay = Long.getLong(Constants.CONFIG_PROP_PREFIX + "http.delay", 0);
+        } catch (NumberFormatException e) {
+            Constants.delay = 0;
+        }
+    }
+
+    /**
+     * Custom delay in milliseconds before an HTTP request goes through.
+     * Used by {@link org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor}
+     */
+    public static final long HTTP_DELAY = delay;
+
+    /**
+     * Handle to OSGI console
+     */
+    public static final String OSGI_CONSOLE = "/system/console";
+
+    /**
+     * General parameters and values
+     */
+    public static final String PARAMETER_CHARSET = "_charset_";
+    public static final String CHARSET_UTF8 = "utf-8";
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClient.java b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
new file mode 100644
index 0000000..7d9bbe3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
@@ -0,0 +1,658 @@
+/*
+ * 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.testing.clients;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.RedirectStrategy;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
+import org.apache.sling.testing.clients.util.FormEntityBuilder;
+import org.apache.sling.testing.clients.util.HttpUtils;
+import org.apache.sling.testing.clients.util.JsonUtils;
+import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.codehaus.jackson.JsonNode;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.http.HttpStatus.SC_CREATED;
+import static org.apache.http.HttpStatus.SC_OK;
+
+/**
+ * <p>The Base class for all Integration Test Clients. It provides generic methods to send HTTP requests to a server. </p>
+ *
+ * <p>It has methods to perform simple node operations on the server like creating and deleting nodes, etc.
+ * on the server using requests. </p>
+ */
+public class SlingClient extends AbstractSlingClient {
+
+    public static final String DEFAULT_NODE_TYPE = "sling:OrderedFolder";
+
+    /**
+     * Constructor used by Builders and adaptTo(). <b>Should never be called directly from the code.</b>
+     *
+     * @param http the underlying HttpClient to be used
+     * @param config sling specific configs
+     * @throws ClientException if the client could not be created
+     *
+     * @see AbstractSlingClient#AbstractSlingClient(CloseableHttpClient, SlingClientConfig)
+     */
+    public SlingClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        super(http, config);
+    }
+
+    /**
+     * <p>Handy constructor easy to use in simple tests. Creates a client that uses basic authentication.</p>
+     *
+     * <p>For constructing clients with complex configurations, use a {@link InternalBuilder}</p>
+     *
+     * <p>For constructing clients with the same configuration, but a different class, use {@link #adaptTo(Class)}</p>
+     *
+     * @param url url of the server (including context path)
+     * @param user username for basic authentication
+     * @param password password for basic authentication
+     * @throws ClientException never, kept for uniformity with the other constructors
+     */
+    public SlingClient(URI url, String user, String password) throws ClientException {
+        super(Builder.create(url, user, password).buildHttpClient(), Builder.create(url, user, password).buildSlingClientConfig());
+    }
+
+    /**
+     * Moves a sling path to a new location (:operation move)
+     *
+     * @param srcPath source path
+     * @param destPath destination path
+     * @param expectedStatus list of accepted status codes in response
+     * @return the response
+     * @throws ClientException if an error occurs during operation
+     */
+    public SlingHttpResponse move(String srcPath, String destPath, int... expectedStatus) throws ClientException {
+        UrlEncodedFormEntity entity = FormEntityBuilder.create()
+                .addParameter(":operation", "move")
+                .addParameter(":dest", destPath)
+                .build();
+
+        return this.doPost(srcPath, entity, expectedStatus);
+    }
+
+    /**
+     * Deletes a sling path (:operation delete)
+     *
+     * @param path path to be deleted
+     * @param expectedStatus list of accepted status codes in response
+     * @return the response
+     * @throws ClientException if an error occurs during operation
+     */
+    public SlingHttpResponse deletePath(String path, int... expectedStatus) throws ClientException {
+        HttpEntity entity = FormEntityBuilder.create().addParameter(":operation", "delete").build();
+
+        return this.doPost(path, entity, expectedStatus);
+    }
+
+    /**
+     * Recursively creates all the none existing nodes in the given path using the {@link SlingClient#createNode(String, String)} method.
+     * All the created nodes will have the given node type.
+     *
+     * @param path the path to use for creating all the none existing nodes
+     * @param nodeType the node type to use for the created nodes
+     * @return the response to the creation of the leaf node
+     * @throws ClientException if one of the nodes can't be created
+     */
+    public SlingHttpResponse createNodeRecursive(final String path, final String nodeType) throws ClientException {
+        final String parentPath = getParentPath(path);
+        if (!parentPath.isEmpty() && !exists(parentPath)) {
+            createNodeRecursive(parentPath, nodeType);
+        }
+
+        return createNode(path, nodeType);
+    }
+
+    /**
+     * Creates the node specified by a given path with the given node type.<br>
+     * If the given node type is {@code null}, the node will be created with the default type: {@value DEFAULT_NODE_TYPE}.<br>
+     * If the node already exists, the method will return null, with no errors.<br>
+     * The method ignores trailing slashes so a path like this <i>/a/b/c///</i> is accepted and will create the <i>c</i> node if the rest of
+     * the path exists.
+     * 
+     * @param path the path to the node to create
+     * @param nodeType the type of the node to create
+     * @return the sling HTTP response or null if the path already existed
+     * @throws ClientException if the node can't be created
+     */
+    public SlingHttpResponse createNode(final String path, final String nodeType) throws ClientException {
+        if (!exists(path)) {
+
+            String nodeTypeValue = nodeType;
+            if (nodeTypeValue == null) {
+                nodeTypeValue = DEFAULT_NODE_TYPE;
+            }
+
+            // Use the property for creating the actual node for working around the Sling issue with dot containing node names.
+            // The request will be similar with doing:
+            // curl -F "nodeName/jcr:primaryType=nodeTypeValue" -u admin:admin http://localhost:8080/nodeParentPath
+            final String nodeName = getNodeNameFromPath(path);
+            final String nodeParentPath = getParentPath(path);
+            final HttpEntity entity = FormEntityBuilder.create().addParameter(nodeName + "/jcr:primaryType", nodeTypeValue).build();
+            return this.doPost(nodeParentPath, entity, SC_OK, SC_CREATED);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>Checks whether a path exists or not by making a GET request to that path with the {@code json} extension</p>
+     * @param path path to be checked
+     * @return true if GET response returns 200
+     * @throws ClientException if the request could not be performed
+     */
+    public boolean exists(String path) throws ClientException {
+        SlingHttpResponse response = this.doGet(path + ".json");
+        final int status = response.getStatusLine().getStatusCode();
+        return status == SC_OK;
+    }
+
+    /**
+     * Extracts the parent path from the given String
+     *
+     * @param path string containing the path
+     * @return the parent path if exists or empty string otherwise
+     */
+    protected String getParentPath(final String path) {
+        // TODO define more precisely what is the parent of a folder and of a file
+        final String normalizedPath = StringUtils.removeEnd(path, "/");  // remove trailing slash in case of folders
+        return StringUtils.substringBeforeLast(normalizedPath, "/");
+    }
+
+    /**
+     * Extracts the node from path
+     *
+     * @param path string containing the path
+     * @return the node without parent path
+     */
+    protected String getNodeNameFromPath(final String path) {
+        // TODO define the output for all the cases (e.g. paths with trailing slash)
+        final String normalizedPath = StringUtils.removeEnd(path, "/");  // remove trailing slash in case of folders
+        final int pos = normalizedPath.lastIndexOf('/');
+        if (pos != -1) {
+            return normalizedPath.substring(pos + 1, normalizedPath.length());
+        }
+        return normalizedPath;
+    }
+
+    /**
+     * <p>Checks whether a path exists or not by making a GET request to that path with the {@code json extension} </p>
+     * <p>It polls the server and waits until the path exists </p>
+     * @param path path to be checked
+     * @param waitMillis time to wait between retries
+     * @param retryCount number of retries before throwing an exception
+     * @throws ClientException if the path was not found
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public void waitUntilExists(final String path, final long waitMillis, int retryCount)
+            throws ClientException, InterruptedException {
+        AbstractPoller poller =  new AbstractPoller(waitMillis, retryCount) {
+            boolean found = false;
+            public boolean call() {
+                try {
+                    found = exists(path);
+                } catch (ClientException e) {
+                    // maybe log
+                    found = false;
+                }
+                return true;
+            }
+
+            public boolean condition() {
+                return found;
+            }
+        };
+
+        boolean found = poller.callUntilCondition();
+        if (!found) {
+            throw new ClientException("path " + path + " does not exist after " + retryCount + " retries");
+        }
+    }
+
+    /**
+     * Sets String component property on a node.
+     *
+     * @param nodePath       path to the node to be edited
+     * @param propName       name of the property to be edited
+     * @param propValue      value of the property to be edited
+     * @param expectedStatus list of expected HTTP Status to be returned, if not set, 200 is assumed.
+     * @return the response object
+     * @throws ClientException if something fails during the request/response cycle
+     */
+    public SlingHttpResponse setPropertyString(String nodePath, String propName, String propValue, int... expectedStatus)
+            throws ClientException {
+        // prepare the form
+        HttpEntity formEntry = FormEntityBuilder.create().addParameter(propName, propValue).build();
+        // send the request
+        return this.doPost(nodePath, formEntry, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+    }
+
+    /**
+     * Sets a String[] component property on a node.
+     *
+     * @param nodePath         path to the node to be edited
+     * @param propName         name of the property to be edited
+     * @param propValueList    List of String values
+     * @param expectedStatus   list of expected HTTP Status to be returned, if not set, 200 is assumed.
+     * @return                 the response
+     * @throws ClientException if something fails during the request/response cycle
+     */
+    public SlingHttpResponse setPropertyStringArray(String nodePath, String propName, List<String> propValueList, int... expectedStatus)
+            throws ClientException {
+        // prepare the form
+        FormEntityBuilder formEntry = FormEntityBuilder.create();
+        for (String propValue : (propValueList != null) ? propValueList : new ArrayList<String>(0)) {
+            formEntry.addParameter(propName, propValue);
+        }
+        // send the request and return the sling response
+        return this.doPost(nodePath, formEntry.build(), HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+    }
+
+    /**
+     * Sets multiple String properties on a node in a single request
+     * @param nodePath path to the node to be edited
+     * @param properties list of NameValue pairs with the name and value for each property. String[] properties can be defined
+     *                   by adding multiple time the same property name with different values
+     * @param expectedStatus list of expected HTTP Status to be returned, if not set, 200 is assumed.
+     * @return the response
+     * @throws ClientException if the operation could not be completed
+     */
+    public SlingHttpResponse setPropertiesString(String nodePath, List<NameValuePair> properties, int... expectedStatus)
+            throws ClientException {
+        // prepare the form
+        HttpEntity formEntry = FormEntityBuilder.create().addAllParameters(properties).build();
+        // send the request and return the sling response
+        return this.doPost(nodePath, formEntry, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+    }
+
+    /**
+     * Returns the JSON content of a node already mapped to a {@link org.codehaus.jackson.JsonNode}.<br>
+     * Waits max 10 seconds for the node to be created.
+     *
+     * @param path  the path to the content node
+     * @param depth the number of levels to go down the tree, -1 for infinity
+     * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
+     * @throws ClientException if something fails during request/response processing
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public JsonNode getJsonNode(String path, int depth) throws ClientException, InterruptedException {
+        return getJsonNode(path, depth, 500, 20);
+    }
+
+    /**
+     * Returns JSON format of a content node already mapped to a {@link org.codehaus.jackson.JsonNode}.
+     *
+     * @param path                 the path to the content node
+     * @param depth                the number of levels to go down the tree, -1 for infinity
+     * @param waitMillis           how long it should wait between requests
+     * @param retryNumber          number of retries before throwing an exception
+     * @param expectedStatus       list of allowed HTTP Status to be returned. If not set,
+     *                             http status 200 (OK) is assumed.
+     * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
+     * @throws ClientException if something fails during request/response cycle
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public JsonNode getJsonNode(String path, int depth, final long waitMillis, final int retryNumber, int... expectedStatus)
+            throws ClientException, InterruptedException {
+
+        // check if path exist and wait if needed
+        waitUntilExists(path, waitMillis, retryNumber);
+
+        // check for infinity
+        if (depth == -1) {
+            path += ".infinity.json";
+        } else {
+            path += "." + depth + ".json";
+        }
+
+        // request the JSON for the page node
+        SlingHttpResponse response = this.doGet(path);
+        HttpUtils.verifyHttpStatus(response, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+
+        return JsonUtils.getJsonNodeFromString(response.getContent());
+    }
+
+    /**
+     * Uploads a file to the repository. It creates a leaf node typed {@code nt:file}. The intermediary nodes are created with
+     * type "sling:OrderedFolder" if parameter {@code createFolders} is true
+     *
+     * @param file           the file to be uploaded
+     * @param mimeType       the MIME Type of the file
+     * @param toPath         the complete path of the file in the repository including file name
+     * @param createFolders  if true, all non existing parent nodes will be created using node type {@code sling:OrderedFolder}
+     * @param expectedStatus list of expected HTTP Status to be returned, if not set, 201 is assumed.
+     * @return               the response
+     * @throws ClientException if something fails during the request/response cycle
+     */
+    public SlingHttpResponse upload(File file, String mimeType, String toPath, boolean createFolders, int... expectedStatus)
+            throws ClientException {
+        // Determine filename and parent folder, depending on whether toPath is a folder or a file
+        String toFileName;
+        String toFolder;
+        if (toPath.endsWith("/")) {
+            toFileName = file.getName();
+            toFolder = toPath;
+        } else {
+            toFileName = getNodeNameFromPath(toPath);
+            toFolder = getParentPath(toPath);
+        }
+
+        if (createFolders) {
+            createNodeRecursive(toFolder, "sling:OrderedFolder");
+        }
+
+        if (mimeType == null) {
+            mimeType = "application/octet-stream";
+        }
+
+        HttpEntity entity = MultipartEntityBuilder.create()
+                .addBinaryBody(toFileName, file, ContentType.create(mimeType), toFileName)
+                .build();
+
+        // return the sling response
+        return this.doPost(toFolder, entity, HttpUtils.getExpectedStatus(SC_CREATED, expectedStatus));
+    }
+
+    /**
+     * Creates a new Folder of type sling:OrderedFolder. Same as using {@code New Folder...} in the Site Admin.
+     *
+     * @param folderName     The name of the folder to be used in the URL.
+     * @param folderTitle    Title of the Folder to be set in jcr:title
+     * @param parentPath     The parent path where the folder gets added.
+     * @param expectedStatus list of expected HTTP Status to be returned, if not set, 201 is assumed.
+     * @return the response
+     * @throws ClientException if something fails during the request/response cycle
+     */
+    public SlingHttpResponse createFolder(String folderName, String folderTitle, String parentPath, int... expectedStatus)
+            throws ClientException {
+        // we assume the parentPath is a folder, even though it doesn't end with a slash
+        parentPath = StringUtils.appendIfMissing(parentPath, "/");
+        String folderPath = parentPath + folderName;
+        HttpEntity feb = FormEntityBuilder.create()
+                .addParameter("./jcr:primaryType", "sling:OrderedFolder")  // set primary type for folder node
+                .addParameter("./jcr:content/jcr:primaryType", "nt:unstructured")  // add jcr:content as sub node
+                .addParameter("./jcr:content/jcr:title", folderTitle)  //set the title
+                .build();
+
+        // execute request and return the sling response
+        return this.doPost(folderPath, feb, HttpUtils.getExpectedStatus(SC_CREATED, expectedStatus));
+    }
+
+    /**
+     * Get uuid from any repository path
+     *
+     * @param repPath path in repository
+     * @return uuid as String
+     * @throws ClientException if something fails during request/response cycle
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public String getUUID(String repPath) throws ClientException, InterruptedException {
+        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
+        if (!exists(repPath)) {
+            return null;
+        }
+        JsonNode jsonNode = getJsonNode(repPath, -1);
+        return getUUId(jsonNode);
+    }
+
+    /**
+     * Get uuid from any repository path
+     *
+     * @param jsonNode {@link JsonNode} in repository
+     * @return uuid as String or null if jsonNode is null or if the uuid was not found
+     * @throws ClientException if something fails during request/response cycle
+     */
+    public String getUUId(JsonNode jsonNode) throws ClientException {
+        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
+        if (jsonNode == null) {
+            return null;  // node does not exist
+        }
+
+        JsonNode uuidNode = jsonNode.get("jcr:uuid");
+
+        if (uuidNode == null) {
+            return null;
+        }
+
+        return uuidNode.getValueAsText();
+    }
+
+    //
+    // InternalBuilder class and builder related methods
+    //
+
+    /**
+     * <p>Extensible InternalBuilder for SlingClient. Can be used by calling: {@code SlingClient.builder().create(...).build()}.
+     * Between create() and build(), any number of <i>set</i> methods can be called to customize the client.<br>
+     * It also exposes the underling httpClientBuilder through {@link #httpClientBuilder()} which can be used to customize the client
+     * at http level.
+     * </p>
+     *
+     * <p>The InternalBuilder is created to be easily extensible. A class, e.g. {@code MyClient extends SlingClient}, can have its own InternalBuilder.
+     * This is worth creating if MyClient has fields that need to be initialized. The Skeleton of such InternalBuilder (created inside MyClient) is:
+     * </p>
+     * <blockquote><pre>
+     * {@code
+     * public static abstract class InternalBuilder<T extends MyClient> extends SlingClient.InternalBuilder<T> {
+     *     private String additionalField;
+     *
+     *     public InternalBuilder(URI url, String user, String password) { super(url, user, password); }
+     *
+     *     public InternalBuilder<T> setAdditionalField(String s) { additionalField = s; }
+     * }
+     * }
+     * </pre></blockquote>
+     * <p>Besides this, two more methods need to be implemented directly inside {@code MyClient}: </p>
+     * <blockquote><pre>
+     * {@code
+     * public static InternalBuilder<?> builder(URI url, String user, String password) {
+     *     return new InternalBuilder<MyClient>(url, user, password) {
+     *         {@literal @}Override
+     *         public MyClient build() throws ClientException { return new MyClient(this); }
+     *     };
+     * }
+     *
+     * protected MyClient(InternalBuilder<MyClient> builder) throws ClientException {
+     *   super(builder);
+     *   additionalField = builder.additionalField;
+     * }
+     * }
+     * </pre></blockquote>
+     * Of course, the Clients and InternalBuilder are extensible on several levels, so MyClient.InternalBuilder can be further extended.
+     *
+     * @param <T> type extending SlingClient
+     */
+    public static abstract class InternalBuilder<T extends SlingClient> {
+
+        private final SlingClientConfig.Builder configBuilder;
+
+        private final HttpClientBuilder httpClientBuilder;
+
+        protected InternalBuilder(URI url, String user, String password) {
+            this.httpClientBuilder = HttpClientBuilder.create();
+            this.configBuilder = SlingClientConfig.Builder.create().setUrl(url).setUser(user).setPassword(password);
+
+            setDefaults();
+        }
+
+        public InternalBuilder<T> setUrl(URI url) {
+            this.configBuilder.setUrl(url);
+            return this;
+        }
+
+        public InternalBuilder<T> setUser(String user) {
+            this.configBuilder.setUser(user);
+            return this;
+        }
+
+        public InternalBuilder<T> setPassword(String password) {
+            this.configBuilder.setPassword(password);
+            return this;
+        }
+
+        public InternalBuilder<T> setCredentialsProvider(CredentialsProvider cp) {
+            this.configBuilder.setCredentialsProvider(cp);
+            return this;
+        }
+
+        public InternalBuilder<T> setCookieStore(CookieStore cs) {
+            this.configBuilder.setCookieStore(cs);
+            return this;
+        }
+
+        public HttpClientBuilder httpClientBuilder() {
+            return httpClientBuilder;
+        }
+
+        public abstract T build() throws ClientException;
+
+        protected CloseableHttpClient buildHttpClient() {
+            return httpClientBuilder.build();
+        }
+
+        protected SlingClientConfig buildSlingClientConfig() {
+            return configBuilder.build();
+        }
+
+        /**
+         * Sets defaults to the builder.
+         *
+         * @return this
+         */
+        private InternalBuilder setDefaults() {
+            httpClientBuilder.useSystemProperties();
+            httpClientBuilder.setUserAgent("Java");
+            // Connection
+            httpClientBuilder.setMaxConnPerRoute(10);
+            httpClientBuilder.setMaxConnTotal(100);
+            // Interceptors
+            httpClientBuilder.addInterceptorLast(new DelayRequestInterceptor(Constants.HTTP_DELAY));
+
+            return this;
+        }
+
+        //
+        // HttpClientBuilder delegating methods
+        //
+
+        public final InternalBuilder<T> addInterceptorFirst(final HttpResponseInterceptor itcp) {
+            httpClientBuilder.addInterceptorFirst(itcp);
+            return this;
+        }
+
+        /**
+         * Adds this protocol interceptor to the tail of the protocol processing list.
+         * <p>
+         * Please note this value can be overridden by the {@link HttpClientBuilder#setHttpProcessor(
+         * org.apache.http.protocol.HttpProcessor)} method.
+         * </p>
+         *
+         * @param itcp the interceptor
+         * @return this
+         */
+        public final InternalBuilder<T> addInterceptorLast(final HttpResponseInterceptor itcp) {
+            httpClientBuilder.addInterceptorLast(itcp);
+            return this;
+        }
+
+        /**
+         * Adds this protocol interceptor to the head of the protocol processing list.
+         * <p>
+         * Please note this value can be overridden by the {@link HttpClientBuilder#setHttpProcessor(
+         * org.apache.http.protocol.HttpProcessor)} method.
+         * </p>
+         *
+         * @param itcp the interceptor
+         * @return this
+         */
+        public final InternalBuilder<T> addInterceptorFirst(final HttpRequestInterceptor itcp) {
+            httpClientBuilder.addInterceptorFirst(itcp);
+            return this;
+        }
+
+        /**
+         * Adds this protocol interceptor to the tail of the protocol processing list.
+         * <p>
+         * Please note this value can be overridden by the {@link HttpClientBuilder#setHttpProcessor(
+         * org.apache.http.protocol.HttpProcessor)} method.
+         * </p>
+         *
+         * @param itcp the interceptor
+         * @return this
+         */
+        public final InternalBuilder<T> addInterceptorLast(final HttpRequestInterceptor itcp) {
+            httpClientBuilder.addInterceptorLast(itcp);
+            return this;
+        }
+
+        /**
+         * Assigns {@link RedirectStrategy} instance.
+         * <p>Please note this value can be overridden by the {@link #disableRedirectHandling()} method.</p>
+         *
+         * @param redirectStrategy custom redirect strategy
+         * @return this
+         */
+        public final InternalBuilder<T> setRedirectStrategy(final RedirectStrategy redirectStrategy) {
+            httpClientBuilder.setRedirectStrategy(redirectStrategy);
+            return this;
+        }
+
+        /**
+         * Disables automatic redirect handling.
+         *
+         * @return this
+         */
+        public final InternalBuilder<T> disableRedirectHandling() {
+            httpClientBuilder.disableRedirectHandling();
+            return this;
+        }
+
+    }
+
+    public final static class Builder extends InternalBuilder<SlingClient> {
+
+        private Builder(URI url, String user, String password) {
+            super(url, user, password);
+        }
+
+        @Override
+        public SlingClient build() throws ClientException {
+            return new SlingClient(buildHttpClient(), buildSlingClientConfig());
+        }
+
+        public static Builder create(URI url, String user, String password) {
+            return new Builder(url, user, password);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClientConfig.java b/src/main/java/org/apache/sling/testing/clients/SlingClientConfig.java
new file mode 100644
index 0000000..58d380c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClientConfig.java
@@ -0,0 +1,227 @@
+/*
+ * 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.testing.clients;
+
+import com.google.common.base.Strings;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@ThreadSafe
+public class SlingClientConfig {
+
+    /**
+     * Base URI of the server under test.
+     */
+    protected final URI url;
+
+    /**
+     * Name of the user that will be used to authenticate the requests.
+     */
+    protected final String user;
+
+    /**
+     * Password of the user that will be used to authenticate the requests.
+     */
+    protected final String password;
+
+    /**
+     * The cookie store
+     */
+    protected final CookieStore cookieStore;
+
+    /**
+     * The credentials provider
+     */
+    protected final CredentialsProvider credsProvider;
+
+    /**
+     * AuthCache for preemptive auth
+     */
+    protected final AuthCache authCache;
+
+    /**
+     * Extra values to be used in interceptors, custom auth mechanisms, etc.
+     */
+    protected final Map<String, String> values;
+
+    protected SlingClientConfig(URI url, String user, String password,
+                                CookieStore cookieStore,
+                                CredentialsProvider credentialsProvider, AuthCache authCache) {
+        this.url = url;
+        this.user = user;
+        this.password = password;
+
+        this.cookieStore = cookieStore;
+        this.credsProvider = credentialsProvider;
+        this.authCache = authCache;
+
+        this.values = new ConcurrentHashMap<String, String>();
+    }
+
+    /**
+     * @return the base URL that the sling client is pointing to. It should always end with a "/"
+     */
+    public URI getUrl() {
+        return url;
+    }
+
+    /**
+     * @return the user that the client is using.
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * @return the user that the client is using.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * <p>Get the map of extra custom values configured on the client</p>
+     * <p>These may be used by interceptors, for example</p>
+     *
+     * @return the reference to the map
+     */
+    public Map<String, String> getValues() {
+        return values;
+    }
+
+    /**
+     * @return a reference to the cookie store used by the client
+     */
+    public CookieStore getCookieStore() {
+        return cookieStore;
+    }
+
+    /**
+     * @return the reference to the CredentialsProvider used by the client
+     */
+    public CredentialsProvider getCredsProvider() {
+        return credsProvider;
+    }
+
+    /**
+     * @return the reference the AuthCache used by the client
+     */
+    public AuthCache getAuthCache() {
+        return authCache;
+    }
+
+    public static class Builder {
+        protected URI url;
+
+        protected String user;
+
+        protected String password;
+
+        protected CookieStore cookieStore;
+
+        protected CredentialsProvider credsProvider;
+
+        protected AuthCache authCache;
+
+        protected Builder() {
+        }
+
+        public static Builder create() {
+            return new Builder();
+        }
+
+        public Builder setUrl(String url) throws URISyntaxException {
+            return setUrl(new URI(url));
+        }
+
+        public Builder setUrl(URI url) {
+            this.url = url;
+            // Add / as path if none is present
+            if (Strings.isNullOrEmpty(this.url.getPath()) || !this.url.getPath().endsWith("/")) {
+                this.url = this.url.resolve(Strings.nullToEmpty(this.url.getPath()) + "/");
+            }
+            return this;
+        }
+
+        public Builder setUser(String user) {
+            this.user = user;
+            return this;
+        }
+
+        public Builder setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+
+        public Builder setCredentialsProvider(CredentialsProvider credsProvider) {
+            this.credsProvider = credsProvider;
+            return this;
+        }
+
+        public Builder setAuthCache(AuthCache authCache) {
+            this.authCache = authCache;
+            return this;
+        }
+
+        public Builder setCookieStore(CookieStore cookieStore) {
+            this.cookieStore = cookieStore;
+            return this;
+        }
+
+        public SlingClientConfig build() {
+            // Create default CredentialsProvider if not set
+            if (credsProvider == null) {
+                credsProvider = new BasicCredentialsProvider();
+                if (StringUtils.isNotEmpty(this.user)) {
+                    HttpHost targetHost = URIUtils.extractHost(this.url);
+                    credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()),
+                            new UsernamePasswordCredentials(this.user, this.password));
+                }
+            }
+
+            // Create default AuthCache if not set
+            if (authCache == null) {
+                BasicScheme basicScheme = new BasicScheme();
+                authCache = new BasicAuthCache();
+                authCache.put(URIUtils.extractHost(url), basicScheme);
+            }
+
+            // Create default CookieStore if not set
+            if (cookieStore == null) {
+                cookieStore = new BasicCookieStore();
+            }
+
+            return new SlingClientConfig(url, user, password, cookieStore, credsProvider, authCache);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingHttpResponse.java b/src/main/java/org/apache/sling/testing/clients/SlingHttpResponse.java
new file mode 100644
index 0000000..9a54df6
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/SlingHttpResponse.java
@@ -0,0 +1,396 @@
+/*
+ * 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.testing.clients;
+
+import org.apache.http.*;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Pattern;
+
+public class SlingHttpResponse implements CloseableHttpResponse {
+
+    public static final String STATUS = "Status";
+    public static final String MESSAGE = "Message";
+    public static final String LOCATION = "Location";
+    public static final String PARENT_LOCATION = "ParentLocation";
+    public static final String PATH = "Path";
+    public static final String REFERER = "Referer";
+    public static final String CHANGE_LOG = "ChangeLog";
+
+    private final CloseableHttpResponse httpResponse;
+    private String content;
+
+    public SlingHttpResponse(CloseableHttpResponse response) {
+        this.httpResponse = response;
+    }
+
+    /**
+     * <p>Get the {@code String} content of the response.</p>
+     * <p>The content is cached so it is safe to call this method several times.</p>
+     * <p><b>Attention!</b> Calling this method consumes the entity, so it cannot be used as an InputStream later</p>
+     *
+     * @return the content as String
+     */
+    public String getContent() {
+        if (!this.isConsumed()) {
+            try {
+                this.content = EntityUtils.toString(this.getEntity());
+                this.close();
+            } catch (IOException e) {
+                throw new RuntimeException("Could not read content from response", e);
+            }
+        }
+
+        return content;
+    }
+
+    public boolean isConsumed() {
+        return this.content != null || this.getEntity() == null;
+    }
+
+    /**
+     * <p>Assert that response matches supplied status</p>
+     *
+     * @param expected the expected http status
+     * @throws AssertionError if the response does not match the expected
+     */
+    public void checkStatus(int expected) throws ClientException {
+        if (this.getStatusLine().getStatusCode() != expected) {
+            throw new ClientException(this + " has wrong response status ("
+                    + this.getStatusLine().getStatusCode() + "). Expected " + expected);
+        }
+    }
+
+    /**
+     * <p>Assert that response matches supplied content type (from Content-Type header)</p>
+     *
+     * @param expected the expected content type
+     * @throws AssertionError if the response content type does not match the expected
+     */
+    public void checkContentType(String expected) throws ClientException {
+        // Remove whatever follows semicolon in content-type
+        String contentType = this.getEntity().getContentType().getValue();
+        if (contentType != null) {
+            contentType = contentType.split(";")[0].trim();
+        }
+
+        // check for match
+        if (!contentType.equals(expected)) {
+            throw new ClientException(this + " has wrong content type (" + contentType + "). Expected " + expected);
+        }
+    }
+
+    /**
+     * <p>For each regular expression, assert that at least one line of the response matches the expression</p>
+     * <p>The regular expressions are automatically prefixed and suffixed with .* it order to partial-match the lines</p>
+     *
+     * @param regexp list of regular expressions
+     * @throws AssertionError if the response content does not match one of the regexp
+     */
+    public void checkContentRegexp(String... regexp) throws ClientException {
+        for(String expr : regexp) {
+            final Pattern p = Pattern.compile(".*" + expr + ".*");
+            final Scanner scanner = new Scanner(this.getContent());
+            boolean matched = false;
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                if (p.matcher(line).matches()) {
+                    matched = true;
+                    break;
+                }
+            }
+
+            if (!matched) {
+                throw new ClientException("Pattern " + p + " didn't match any line in content");
+            }
+        }
+    }
+
+    /**
+     * <p>Assert that all the provided {@code Strings} are contained in the response</p>
+     *
+     * @param expected list of expected strings
+     */
+    public void checkContentContains(String... expected) throws ClientException {
+        for (String s : expected) {
+            if (!this.getContent().contains(s)) {
+                throw new ClientException("Content does not contain string " + s + ". Content is: \n\n" + getContent());
+            }
+        }
+    }
+
+    /**
+     * Get status from Sling Response
+     *
+     * @return Sling Status
+     */
+    public String getSlingStatus() {
+        String searchPattern = "id=\"" + STATUS + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get status from Sling Response as integer
+     *
+     * @return Sling Status
+     */
+    public int getSlingStatusAsInt() throws NumberFormatException {
+        String strStatus = getSlingStatus();
+        return Integer.parseInt(strStatus);
+    }
+
+    /**
+     * Get message from Sling Response
+     *
+     * @return Sling Message
+     */
+    public String getSlingMessage() {
+        String searchPattern = "id=\"" + MESSAGE + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get copy paths from message
+     *
+     * @return copy paths as String Array
+     */
+    public String[] getSlingCopyPaths() {
+        String copyPaths = getSlingMessage();
+        StringTokenizer tokenizer = new StringTokenizer(copyPaths);
+        List<String> copies = new ArrayList<String>();
+        while (tokenizer.hasMoreElements()) {
+            copies.add(tokenizer.nextToken());
+        }
+        return copies.toArray(new String[copies.size()]);
+    }
+
+    /**
+     * Get location from Sling Response
+     *
+     * @return Sling Location
+     */
+    public String getSlingLocation() {
+        String searchPattern = "id=\"" + LOCATION + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get parent location from Sling Response
+     *
+     * @return Sling Parent Location
+     */
+    public String getSlingParentLocation() {
+        String searchPattern = "id=\"" + PARENT_LOCATION + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get path from Sling Response
+     *
+     * @return Sling Path
+     */
+    public String getSlingPath() {
+        String searchPattern = "id=\"" + PATH + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get referer from Sling Response
+     *
+     * @return Sling Referer
+     */
+    public String getSlingReferer() {
+        String searchPattern = "id=\"" + REFERER + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Get change log from Sling Response
+     *
+     * @return Sling Change Log
+     */
+    public String getSlingChangeLog() {
+        String searchPattern = "id=\"" + CHANGE_LOG + "\">";
+        return extractFromHTMLResponse(searchPattern);
+    }
+
+    /**
+     * Extract information from response
+     *
+     * @param searchPattern search pattern to look for
+     * @return Sling information
+     */
+    protected String extractFromHTMLResponse(String searchPattern) {
+        String tmpResponse = null;
+        int start = getContent().indexOf(searchPattern);
+        if (start > 0) {
+            start += searchPattern.length();
+            tmpResponse = getContent().substring(start);
+            int end = tmpResponse.indexOf("<");
+            tmpResponse = tmpResponse.substring(0, end);
+        }
+        return tmpResponse;
+    }
+
+    // HttpResponse delegated methods
+
+    @Override
+    public StatusLine getStatusLine() {
+        return httpResponse.getStatusLine();
+    }
+
+    @Override
+    public void setStatusLine(StatusLine statusline) {
+        httpResponse.setStatusLine(statusline);
+    }
+
+    @Override
+    public void setStatusLine(ProtocolVersion ver, int code) {
+        httpResponse.setStatusLine(ver, code);
+    }
+
+    @Override
+    public void setStatusLine(ProtocolVersion ver, int code, String reason) {
+        httpResponse.setStatusLine(ver, code, reason);
+    }
+
+    @Override
+    public void setStatusCode(int code) throws IllegalStateException {
+        httpResponse.setStatusCode(code);
+    }
+
+    @Override
+    public void setReasonPhrase(String reason) throws IllegalStateException {
+        httpResponse.setReasonPhrase(reason);
+    }
+
+    @Override
+    public HttpEntity getEntity() {
+        return httpResponse.getEntity();
+    }
+
+    @Override
+    public void setEntity(HttpEntity entity) {
+        httpResponse.setEntity(entity);
+    }
+
+    @Override
+    public Locale getLocale() {
+        return httpResponse.getLocale();
+    }
+
+    @Override
+    public void setLocale(Locale loc) {
+        httpResponse.setLocale(loc);
+    }
+
+    @Override
+    public ProtocolVersion getProtocolVersion() {
+        return httpResponse.getProtocolVersion();
+    }
+
+    @Override
+    public boolean containsHeader(String name) {
+        return httpResponse.containsHeader(name);
+    }
+
+    @Override
+    public Header[] getHeaders(String name) {
+        return httpResponse.getHeaders(name);
+    }
+
+    @Override
+    public Header getFirstHeader(String name) {
+        return httpResponse.getFirstHeader(name);
+    }
+
+    @Override
+    public Header getLastHeader(String name) {
+        return httpResponse.getLastHeader(name);
+    }
+
+    @Override
+    public Header[] getAllHeaders() {
+        return httpResponse.getAllHeaders();
+    }
+
+    @Override
+    public void addHeader(Header header) {
+        httpResponse.addHeader(header);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        httpResponse.addHeader(name, value);
+    }
+
+    @Override
+    public void setHeader(Header header) {
+        httpResponse.setHeader(header);
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        httpResponse.setHeader(name, value);
+    }
+
+    @Override
+    public void setHeaders(Header[] headers) {
+        httpResponse.setHeaders(headers);
+    }
+
+    @Override
+    public void removeHeader(Header header) {
+        httpResponse.removeHeader(header);
+    }
+
+    @Override
+    public void removeHeaders(String name) {
+        httpResponse.removeHeaders(name);
+    }
+
+    @Override
+    public HeaderIterator headerIterator() {
+        return httpResponse.headerIterator();
+    }
+
+    @Override
+    public HeaderIterator headerIterator(String name) {
+        return httpResponse.headerIterator(name);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public HttpParams getParams() {
+        return httpResponse.getParams();
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setParams(HttpParams params) {
+        httpResponse.setParams(params);
+    }
+
+    @Override
+    public void close() throws IOException {
+        httpResponse.close();
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java b/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
new file mode 100644
index 0000000..168e6ab
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.instance;
+
+import java.net.URI;
+
+/**
+ * Configuration of a single instance instance.
+ */
+public class InstanceConfiguration {
+
+    private URI url;
+    private final String runmode;
+
+    public InstanceConfiguration(final URI url, final String runmode) {
+        this.url = url;
+        this.runmode = runmode;
+    }
+
+    public URI getUrl() {
+        return this.url;
+    }
+
+    public String getRunmode() {
+        return runmode;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java b/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
new file mode 100644
index 0000000..de4e5a3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
@@ -0,0 +1,88 @@
+/*
+ * 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.testing.clients.instance;
+
+import org.apache.sling.testing.clients.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for getting the current instance setup
+ */
+public final class InstanceSetup {
+
+    private static final Logger LOG = LoggerFactory.getLogger(InstanceSetup.class);
+    private static InstanceSetup SINGLETON;
+
+    /**
+     * @return  the current setup object.
+     */
+    public static InstanceSetup get() {
+        if ( SINGLETON == null ) {
+            SINGLETON = new InstanceSetup();
+        }
+        return SINGLETON;
+    }
+
+    private final List<InstanceConfiguration> configs = new ArrayList<InstanceConfiguration>();
+
+    private InstanceSetup() {
+        final int number = Integer.valueOf(System.getProperty(Constants.CONFIG_PROP_PREFIX + "instances", "0"));
+        for (int i=1; i<=number; i++ ) {
+            URI url;
+            try {
+                url = new URI(System.getProperty(Constants.CONFIG_PROP_PREFIX + "instance.url." + String.valueOf(i)));
+            } catch (URISyntaxException e) {
+                LOG.error("Could not read URL for instance");
+                continue;
+            }
+            final String runmode = System.getProperty(Constants.CONFIG_PROP_PREFIX + "instance.runmode." + String.valueOf(i));
+
+            final InstanceConfiguration qc = new InstanceConfiguration(url, runmode);
+
+            this.configs.add(qc);
+        }
+    }
+
+    /**
+     * @return all instance configurations.
+     */
+    public List<InstanceConfiguration> getConfigurations() {
+        return this.configs;
+    }
+
+    /**
+     * Get the list of all InstanceConfiguration with a specific {@code runmode}
+     *
+     * @param runmode the desired runmode
+     * @return all instance configurations filtered by runmode.
+     */
+    public List<InstanceConfiguration> getConfigurations(final String runmode) {
+        final List<InstanceConfiguration> result = new ArrayList<InstanceConfiguration>();
+        for(final InstanceConfiguration qc : this.configs) {
+            if ( runmode == null || runmode.equals(qc.getRunmode()) ) {
+                result.add(qc);
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/DelayRequestInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/DelayRequestInterceptor.java
new file mode 100644
index 0000000..ab4b41d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/DelayRequestInterceptor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.testing.clients.interceptors;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+public class DelayRequestInterceptor implements HttpRequestInterceptor {
+
+    private final long milliseconds;
+
+    public DelayRequestInterceptor(long milliseconds) {
+        this.milliseconds = milliseconds;
+    }
+
+    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+        if (milliseconds <= 0) {
+            return;
+        }
+
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {
+            throw new InterruptedIOException();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieHolder.java b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieHolder.java
new file mode 100644
index 0000000..c2b70a9
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieHolder.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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.testing.clients.interceptors;
+
+import org.apache.http.cookie.Cookie;
+import org.apache.sling.testing.clients.Constants;
+
+public class StickyCookieHolder {
+
+    private static final ThreadLocal<Cookie> testStickySessionCookie = new ThreadLocal<Cookie>();
+    public static final String COOKIE_NAME = System.getProperty(Constants.CONFIG_PROP_PREFIX + "session.cookie.name", "test_session_id");
+
+    public static Cookie getTestStickySessionCookie() {
+        return testStickySessionCookie.get();
+    }
+
+    public static void setTestStickySessionCookie(Cookie stickySessionCookie) {
+        testStickySessionCookie.set(stickySessionCookie);
+    }
+
+    public static void remove() {
+        testStickySessionCookie.remove();
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieInterceptor.java
new file mode 100644
index 0000000..42a9948
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieInterceptor.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.testing.clients.interceptors;
+
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+
+public class StickyCookieInterceptor implements HttpRequestInterceptor {
+
+    public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
+        final HttpClientContext clientContext = HttpClientContext.adapt(httpContext);
+        List<Cookie> cookies = clientContext.getCookieStore().getCookies();
+        boolean set = (null != StickyCookieHolder.getTestStickySessionCookie());
+        boolean found = false;
+        ListIterator<Cookie> it = cookies.listIterator();
+        while (it.hasNext()) {
+            Cookie cookie = it.next();
+            if (cookie.getName().equals(StickyCookieHolder.COOKIE_NAME)) {
+                found = true;
+                if (set) {
+                    // set the cookie with the value saved for each thread using the rule
+                    it.set(StickyCookieHolder.getTestStickySessionCookie());
+                } else {
+                    // if the cookie is not set in TestStickySessionRule, remove it from here
+                    it.remove();
+                }
+            }
+        }
+        // if the cookie needs to be set from TestStickySessionRule but did not exist in the client cookie list, add it here.
+        if (!found && set) {
+            cookies.add(StickyCookieHolder.getTestStickySessionCookie());
+        }
+        BasicCookieStore cs = new BasicCookieStore();
+        cs.addCookies(cookies.toArray(new Cookie[cookies.size()]));
+        httpContext.setAttribute(HttpClientContext.COOKIE_STORE, cs);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieSpec.java b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieSpec.java
new file mode 100644
index 0000000..e295459
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieSpec.java
@@ -0,0 +1,47 @@
+/*
+ * 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.testing.clients.interceptors;
+
+import org.apache.http.Header;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.cookie.CookieOrigin;
+import org.apache.http.cookie.CookiePathComparator;
+import org.apache.http.cookie.MalformedCookieException;
+import org.apache.http.impl.cookie.DefaultCookieSpec;
+
+import java.util.List;
+
+public class StickyCookieSpec extends DefaultCookieSpec {
+    private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator();
+
+    @Override
+    public List<Cookie> parse(Header header, CookieOrigin origin) throws MalformedCookieException {
+        List<Cookie> cookies = super.parse(header, origin);
+        for (Cookie cookie : cookies) {
+            if (cookie.getName().equals(StickyCookieHolder.COOKIE_NAME)) {
+                // store it in the TestStickySessionRule threadlocal var
+                StickyCookieHolder.setTestStickySessionCookie(cookie);
+            }
+        }
+        return cookies;
+    }
+
+    @Override
+    public List<Header> formatCookies(List<Cookie> cookies) {
+        return super.formatCookies(cookies);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionHolder.java b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionHolder.java
new file mode 100644
index 0000000..2e5d2df
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionHolder.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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.testing.clients.interceptors;
+
+public class TestDescriptionHolder {
+
+    private static final ThreadLocal<String> methodName = new ThreadLocal<String>();
+    private static final ThreadLocal<String> className = new ThreadLocal<String>();
+
+    public static String getMethodName() {
+        return methodName.get();
+    }
+
+    public static void setMethodName(String methodName) {
+        TestDescriptionHolder.methodName.set(methodName);
+    }
+
+    public static void removeMethodName() {
+        TestDescriptionHolder.methodName.remove();
+    }
+
+    public static String getClassName() {
+        return className.get();
+    }
+
+    public static void setClassName(String className) {
+        TestDescriptionHolder.className.set(className);
+    }
+
+    public static void removeClassName() {
+        TestDescriptionHolder.className.remove();
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
new file mode 100644
index 0000000..38b269e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.testing.clients.interceptors;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+
+/**
+ * HttpClient interceptor that propagates the current test name as part HTTP request headers.
+ * Headers can then be logged, exported as MDC info etc. by {@code TestNameLoggingFilter}.
+ *
+ * Meant to help in correlating the server side logs with the test case being executed.
+ *
+ * @see org.slf4j.MDC http://www.slf4j.org/manual.html
+ */
+public class TestDescriptionInterceptor implements HttpRequestInterceptor{
+    public static final String TEST_NAME_HEADER = "sling.test.name";
+    public static final String TEST_CLASS_HEADER = "sling.test.class";
+
+    public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
+            httpRequest.addHeader(TEST_NAME_HEADER, TestDescriptionHolder.getMethodName());
+            httpRequest.addHeader(TEST_CLASS_HEADER, TestDescriptionHolder.getClassName());
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
new file mode 100644
index 0000000..1de07e5
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.interceptors;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/Bundle.java b/src/main/java/org/apache/sling/testing/clients/osgi/Bundle.java
new file mode 100644
index 0000000..729130e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/Bundle.java
@@ -0,0 +1,55 @@
+/*
+ * 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.testing.clients.osgi;
+
+/**
+ * Thin Wrapper around a Bundle definition JSON
+ */
+public class Bundle {
+
+    public enum Status {
+
+        ACTIVE("Active"),
+
+        FRAGMENT("Fragment"),
+
+        RESOLVED("Resolved"),
+
+        INSTALLED("Installed");
+
+        String value;
+
+        Status(String value) {
+            this.value = value;
+        }
+
+        public static Status value(String o) {
+            for(Status s : values()) {
+                if(s.value.equalsIgnoreCase(o)) {
+                    return s;
+                }
+            }
+            return null;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundleInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundleInfo.java
new file mode 100644
index 0000000..33cdd22
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundleInfo.java
@@ -0,0 +1,124 @@
+/*
+ * 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.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class BundleInfo {
+
+    private JsonNode bundle;
+
+    public BundleInfo(JsonNode root) throws ClientException {
+        if(root.get("id") != null) {
+            if(root.get("id") == null) {
+                throw new ClientException("No Bundle Info returned");
+            }
+            bundle = root;
+        } else {
+            if(root.get("data") == null && root.get("data").size() < 1) {
+                throw new ClientException("No Bundle Info returned");
+            }
+            bundle = root.get("data").get(0);
+        }
+    }
+
+    /**
+     * @return the bundle identifier
+     */
+    public int getId() {
+        return bundle.get("id").getIntValue();
+    }
+
+    /**
+     * @return the bundle name
+     */
+    public String getName() {
+        return bundle.get("name").getTextValue();
+    }
+
+    /**
+     * @return the bundle version
+     */
+    public String getVersion() {
+        return bundle.get("version").getTextValue();
+    }
+
+    /**
+     * Returns the indicator if the bundle is a fragment
+     * 
+     * @return {@code true} if bundle is a fragment, {@code false} otherwise.
+     */
+    public boolean isFragment() {
+        return bundle.get("fragment").getBooleanValue();
+    }
+
+    /**
+     * @return the bundle current state
+     */
+    public Bundle.Status getStatus() {
+        return Bundle.Status.value(bundle.get("state").getTextValue());
+    }
+
+    /**
+     * @return the bundle symbolic name
+     */
+    public String getSymbolicName() {
+        return bundle.get("symbolicName").getTextValue();
+    }
+
+    /**
+     * @return the category of the bundle
+     */
+    public String getCategory() {
+        return bundle.get("category").getTextValue();
+    }
+
+    /**
+     * Returns the value of a specific key in the bundle
+     *
+     * @param key the property to search
+     * @return a specific bundle property
+     */
+    public String getProperty(String key) {
+        Map<String, String> props = getProperties();
+        return props.get(key);
+    }
+
+    /**
+     * @return the bundle properties in a {@link Map}
+     */
+    public Map<String, String> getProperties() {
+        JsonNode props = bundle.get("props");
+        Map<String, String> entries = new HashMap<String, String>();
+
+        if(props != null) {
+            Iterator<JsonNode> it = props.getElements();
+            while(it.hasNext()) {
+                JsonNode n = it.next();
+                entries.put(n.get("key").getTextValue(), n.get("value").getTextValue());
+            }
+        }
+        return entries;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
new file mode 100644
index 0000000..7ada2da
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
@@ -0,0 +1,144 @@
+/*
+ * 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.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+import java.util.Iterator;
+
+/**
+ * A simple Wrapper around the returned JSON when requesting the status of /system/console/bundles
+ */
+public class BundlesInfo {
+
+    private JsonNode root = null;
+
+    private JsonNode status = null;
+
+    /**
+     * The only constructor.
+     * 
+     * @param root the root JSON node of the bundles info.
+     * @throws ClientException if the json does not contain the proper info
+     */
+    public BundlesInfo(JsonNode root) throws ClientException {
+        this.root = root;
+        // some simple sanity checks
+        if(root.get("s") == null)
+            throw new ClientException("No Status Info returned!");
+        if(root.get("s").size() != 5)
+            throw new ClientException("Wrong number of status numbers listed!");
+        status = root.get("s");
+    }
+
+    /**
+     * @return the status message of the bundle context
+     * @throws ClientException if the request cannot be completed
+     */
+    public String getStatusMessage() throws ClientException {
+        if(root.get("status") == null)
+            throw new ClientException("No Status message returned!");
+        return root.get("status").getValueAsText();
+    }
+
+    /**
+     * @return total number of bundles.
+     */
+    public int getTotalNumOfBundles() {
+        return Integer.parseInt(status.get(0).getValueAsText());
+    }
+
+    /**
+     * Returns number of bundles that are in specified state
+     *
+     * @param status the requested status
+     * @return the number of bundles
+     */
+    public int getNumBundlesByStatus(Bundle.Status status) {
+        int index = -1;
+        switch(status) {
+        case ACTIVE:
+            index = 1;
+            break;
+        case FRAGMENT:
+            index = 2;
+            break;
+        case RESOLVED:
+            index = 3;
+            break;
+        case INSTALLED:
+            index = 4;
+            break;
+        }
+        return Integer.parseInt(this.status.get(index).getValueAsText());
+    }
+
+    /**
+     * Return bundle info for a bundle with persistence identifier {@code pid}
+     *
+     * @param id the id of the bundle
+     * @return the BundleInfo
+     * @throws ClientException if the info could not be retrieved
+     */
+    public BundleInfo forId(String id) throws ClientException {
+        JsonNode bundle = findBy("id", id);
+        return (bundle != null) ? new BundleInfo(bundle) : null;
+    }
+
+    /**
+     * Return bundle info for a bundle with name {@code name}
+     *
+     * @param name the name of the requested bundle
+     * @return the info, or {@code null} if the bundle is not found
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public BundleInfo forName(String name) throws ClientException {
+        JsonNode bundle = findBy("name", name);
+        return (bundle != null) ? new BundleInfo(bundle) : null;
+    }
+
+    /**
+     * Return bundle info for a bundle with symbolic name {@code name}
+     *
+     * @param name the symbolic name of the requested bundle
+     * @return the info, or {@code null} if the bundle is not found
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public BundleInfo forSymbolicName(String name) throws ClientException {
+        JsonNode bundle = findBy("symbolicName", name);
+        return (bundle != null) ? new BundleInfo(bundle) : null;
+    }
+
+    private JsonNode findBy(String key, String value) {
+        Iterator<JsonNode> nodes = root.get("data").getElements();
+        while(nodes.hasNext()) {
+            JsonNode node = nodes.next();
+            if(node.get(key) != null) {
+                if(node.get(key).isValueNode()) {
+                	String valueNode=node.get(key).getTextValue();
+                	if (valueNode.equals(value)){
+                		return node;
+                	}
+                }
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
new file mode 100644
index 0000000..1c737a4
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
@@ -0,0 +1,186 @@
+/*
+ * 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.testing.clients.osgi;
+
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+
+/** Utility that installs and starts additional bundles for testing */ 
+public class BundlesInstaller {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final WebconsoleClient webconsoleClient;
+    public static final String ACTIVE_STATE = "active";
+    
+    public BundlesInstaller(WebconsoleClient wcc) {
+        webconsoleClient = wcc;
+    }
+   
+    public boolean isInstalled(File bundleFile) throws Exception {
+        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
+        try{
+            log.debug("Checking if installed: "+bundleSymbolicName);
+            webconsoleClient.checkBundleInstalled(bundleSymbolicName, 1);
+            // if this succeeds, then there's no need to install again
+            log.debug("Already installed: "+bundleSymbolicName);
+            return true;
+        } catch(AssertionError e) {
+            log.debug("Not yet installed: "+bundleSymbolicName);
+            return false;
+        }
+
+    }
+    
+    /** Check if the installed version matches the one of the bundle (file) **/
+    public boolean isInstalledWithSameVersion(File bundleFile) throws Exception {
+        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
+        final String versionOnServer = webconsoleClient.getBundleVersion(bundleSymbolicName);
+        final String versionInBundle = getBundleVersion(bundleFile);
+        if (versionOnServer.equals(versionInBundle)) {
+            return true;
+        } else {
+            log.info("Bundle installed doesn't match: "+bundleSymbolicName+
+                    ", versionOnServer="+versionOnServer+", versionInBundle="+versionInBundle);
+            return false;
+        }
+    }
+    
+    /** Install a list of bundles supplied as Files */
+    public void installBundles(List<File> toInstall, boolean startBundles) throws Exception {
+        for(File f : toInstall) {
+            final String bundleSymbolicName = getBundleSymbolicName(f);
+            if (isInstalled(f)) {
+                if (f.getName().contains("SNAPSHOT")) {
+                    log.info("Reinstalling (due to SNAPSHOT version): {}", bundleSymbolicName);
+                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                } else if (!isInstalledWithSameVersion(f)) {
+                    log.info("Reinstalling (due to version mismatch): {}", bundleSymbolicName);
+                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                } else {
+                    log.info("Not reinstalling: {}", bundleSymbolicName);
+                    continue;
+                }
+            }
+            webconsoleClient.installBundle(f, startBundles);
+            log.info("Installed: {}", bundleSymbolicName);
+        }
+        
+        // ensure that bundles are re-wired esp. if an existing bundle was updated
+        webconsoleClient.refreshPackages();
+
+        log.info("{} additional bundles installed", toInstall.size());
+    }
+    
+    /** Uninstall a list of bundles supplied as Files */
+    public void uninstallBundles(List<File> toUninstall) throws Exception {
+        for(File f : toUninstall) {
+            final String bundleSymbolicName = getBundleSymbolicName(f);
+            if (isInstalled(f)) {
+                log.info("Uninstalling bundle: {}", bundleSymbolicName);
+                webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+            } else {
+                log.info("Could not uninstall: {} as it never was installed", bundleSymbolicName);
+            }
+        }
+        
+        // ensure that bundles are re-wired esp. if an existing bundle was updated
+        webconsoleClient.refreshPackages();
+
+        log.info("{} additional bundles uninstalled", toUninstall.size());
+    }
+    
+    /** Wait for all bundles specified in symbolicNames list to be installed in the
+     *  remote web console.
+     */
+    public void waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws Exception {
+        log.info("Checking that bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
+        for(String symbolicName : symbolicNames) {
+            webconsoleClient.checkBundleInstalled(symbolicName, timeoutSeconds);
+        }
+    }
+    
+    public void startAllBundles(List<String> symbolicNames, int timeoutSeconds) throws Exception {
+        log.info("Starting bundles (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
+        
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000L;
+        final List<String> toStart = new LinkedList<String>();
+        while(System.currentTimeMillis() < timeout) {
+            toStart.clear();
+            for(String name : symbolicNames) {
+                final String state = webconsoleClient.getBundleState(name);
+                if(!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                    toStart.add(name);
+                    break;
+                }
+            }
+            
+            if(toStart.isEmpty()) {
+                log.info("Ok - all bundles are in the {} state", ACTIVE_STATE);
+                break;
+            }
+            
+            for(String name : toStart) {
+                webconsoleClient.startBundle(name);
+            }
+            
+            Thread.sleep(500L);
+        }
+        
+        if(!toStart.isEmpty()) {
+            throw new Exception("Some bundles did not start: " + toStart);
+        }
+    }
+    
+    public String getBundleSymbolicName(File bundleFile) throws IOException {
+        String name = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if (m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            name = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+        } finally {
+            jis.close();
+        }
+        return name;
+    }
+    
+    public String getBundleVersion(File bundleFile) throws IOException {
+        String version = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if(m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            version = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+        } finally {
+            jis.close();
+        }
+        return version;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/Component.java b/src/main/java/org/apache/sling/testing/clients/osgi/Component.java
new file mode 100644
index 0000000..045160c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/Component.java
@@ -0,0 +1,51 @@
+/*
+ * 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.testing.clients.osgi;
+
+public class Component {
+
+    public enum Status {
+
+        ACTIVE("active"),
+
+        REGISTERED("registered"),
+
+        UNSATISFIED("unsatisfied");
+
+        String value;
+
+        Status(String value) {
+            this.value = value;
+        }
+
+        public static Status value(String o) {
+            for(Status s : values()) {
+                if(s.value.equalsIgnoreCase(o)) {
+                    return s;
+                }
+            }
+            return null;
+        }
+
+        public String toString() {
+            return value;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java
new file mode 100644
index 0000000..32ed94c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.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.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+public class ComponentInfo {
+
+    private JsonNode component;
+
+    public ComponentInfo(JsonNode root) throws ClientException {
+        if(root.get("id") != null) {
+            if(root.get("id") == null) {
+                throw new ClientException("No Component Info returned");
+            }
+            component = root;
+        } else {
+            if(root.get("data") == null && root.get("data").size() < 1) {
+                throw new ClientException("No Component Info returned");
+            }
+            component = root.get("data").get(0);
+        }
+    }
+
+    /**
+     * @return the component identifier
+     */
+    public int getId() {
+        return component.get("id").getIntValue();
+    }
+
+    /**
+     * @return the component name
+     */
+    public String getName() {
+        return component.get("name").getTextValue();
+    }
+
+    /**
+     * @return the component status
+     */
+    public Component.Status getStatus() {
+        return Component.Status.value(component.get("state").getTextValue());
+    }
+
+    /**
+     * @return the component persistent identifier
+     */
+    public String getPid() {
+        return component.get("pid").getTextValue();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
new file mode 100644
index 0000000..174544e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.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.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+import java.util.Iterator;
+
+/**
+ * Thin wrapper around the list of components
+ */
+public class ComponentsInfo {
+
+    private JsonNode root = null;
+
+    /**
+     * The only constructor.
+     * 
+     * @param rootNode the root JSON node of the components info.
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentsInfo(JsonNode rootNode) throws ClientException {
+        this.root = rootNode;
+    }
+
+    /**
+     * @return the number of installed components
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public int getNumberOfInstalledComponents() throws ClientException {
+        if(root.get("status") == null)
+            throw new ClientException("Number of installed Components not defined!");
+        return Integer.parseInt(root.get("status").getValueAsText());
+    }
+
+    /**
+     * @param id the id of the component
+     * @return the ComponentInfo for a component with the identifier {@code id}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forId(String id) throws ClientException {
+        JsonNode component = findBy("id", id);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    /**
+     * @param name the name of the component
+     * @return the ComponentInfo for a component with the name {@code name}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forName(String name) throws ClientException {
+        JsonNode component = findBy("name", name);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    /**
+     * @param pid the pid of the component
+     * @return the ComponentInfo for a component with the pid {@code pid}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forPid(String pid) throws ClientException {
+        JsonNode component = findBy("pid", pid);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    private JsonNode findBy(String key, String value) {
+        Iterator<JsonNode> nodes = root.get("data").getElements();
+        while(nodes.hasNext()) {
+            JsonNode node = nodes.next();
+            if(node.get(key) != null) {
+                if(node.get(key).isValueNode()) {
+                    return node;
+                }
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
new file mode 100644
index 0000000..9c5a928
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -0,0 +1,324 @@
+/*
+ * 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.testing.clients.osgi;
+
+import org.apache.http.Header;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingHttpResponse;
+import org.apache.sling.testing.clients.util.JsonUtils;
+import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.apache.sling.testing.clients.SlingClient;
+import org.apache.sling.testing.clients.SlingClientConfig;
+import org.apache.sling.testing.clients.util.FormEntityBuilder;
+import org.apache.sling.testing.clients.util.HttpUtils;
+import org.codehaus.jackson.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.*;
+
+import static org.apache.http.HttpStatus.SC_MOVED_TEMPORARILY;
+import static org.apache.http.HttpStatus.SC_OK;
+
+/**
+ * A client that wraps the Felix OSGi Web Console REST API calls.
+ */
+public class OsgiConsoleClient extends SlingClient {
+
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiConsoleClient.class);
+    /**
+     * All System Console REST API calls go to /system/console and below
+     */
+    private final String CONSOLE_ROOT_URL = "/system/console";
+
+    /**
+     * The URL for configuration requests
+     */
+    private final String URL_CONFIGURATION = CONSOLE_ROOT_URL + "/configMgr";
+
+    /**
+     * The URL for bundle requests
+     */
+    private final String URL_BUNDLES = CONSOLE_ROOT_URL + "/bundles";
+
+    /**
+     * The URL for components requests
+     */
+    private final String URL_COMPONENTS = CONSOLE_ROOT_URL + "/components";
+
+    /**
+     * Default constructor. Simply calls {@link SlingClient#SlingClient(URI, String, String)}
+     *
+     * @param serverUrl the URL to the server under test
+     * @param userName the user name used for authentication
+     * @param password the password for this user
+     * @throws ClientException if the client cannot be instantiated
+     */
+    public OsgiConsoleClient(URI serverUrl, String userName, String password) throws ClientException {
+        super(serverUrl, userName, password);
+    }
+
+    /**
+     * Constructor used by adaptTo() and InternalBuilder classes. Should not be called directly in the code
+     *
+     * @param http http client to be used for requests
+     * @param config sling specific configs
+     * @throws ClientException if the client cannot be instantiated
+     */
+    public OsgiConsoleClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        super(http, config);
+    }
+
+    /**
+     * Returns the wrapper for the bundles info json
+     *
+     * @param expectedStatus list of accepted statuses of the response
+     * @return all the bundles info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public BundlesInfo getBundlesInfo(int... expectedStatus) throws ClientException {
+        // request the bundles information
+        SlingHttpResponse resp = this.doGet(URL_BUNDLES + ".json", HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        // return the wrapper
+        return new BundlesInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the bundle info json
+     *
+     * @param id the id of the bundle
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the bundle info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public BundleInfo getBundleInfo(String id, int... expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_BUNDLES + "/" + id + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new BundleInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the components info json
+     *
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the components info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public ComponentsInfo getComponentsInfo(int... expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_COMPONENTS + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new ComponentsInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the component info json
+     *
+     * @param id the id of the component
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the component info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public ComponentInfo getComponentInfo(String id, int expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_COMPONENTS + "/" + id + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new ComponentInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns a map of all properties set for the config referenced by the PID, where the map keys
+     * are the property names.
+     *
+     * @param pid the pid of the configuration
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the properties as a map
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public Map<String, Object> getConfiguration(String pid, int... expectedStatus) throws ClientException {
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + pid, null);
+        // check the returned status
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        // get the JSON node
+        JsonNode rootNode = JsonUtils.getJsonNodeFromString(resp.getContent());
+        // go through the params
+        Map<String, Object> props = new HashMap<String, Object>();
+        if(rootNode.get("properties") == null)
+            return props;
+        JsonNode properties = rootNode.get("properties");
+        for(Iterator<String> it = properties.getFieldNames(); it.hasNext();) {
+            String propName = it.next();
+            JsonNode value = properties.get(propName).get("value");
+            if(value != null) {
+                props.put(propName, value.getValueAsText());
+                continue;
+            }
+            value = properties.get(propName).get("values");
+            if(value != null) {
+                Iterator<JsonNode> iter = value.getElements();
+                List<String> list = new ArrayList<String>();
+                while(iter.hasNext()) {
+                    list.add(iter.next().getValueAsText());
+                }
+                props.put(propName, list.toArray(new String[list.size()]));
+            }
+        }
+        return props;
+    }
+
+    /**
+     * Returns a map of all properties set for the config referenced by the PID, where the map keys
+     * are the property names. The method waits until the configuration has been set.
+     *
+     * @param waitCount The number of maximum wait intervals of 500ms.
+     *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
+     * @param pid pid
+     * @param expectedStatus expected response status
+     * @return the config properties
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public Map<String, Object> getConfigurationWithWait(long waitCount, String pid, int... expectedStatus)
+            throws ClientException, InterruptedException {
+        ConfigurationPoller poller = new ConfigurationPoller(500L, waitCount, pid, expectedStatus);
+        if (!poller.callUntilCondition())
+            return getConfiguration(pid, expectedStatus);
+        return poller.getConfig();
+    }
+
+    /**
+     * Sets properties of a config referenced by its PID. the properties to be edited are passed as
+     * a map of property name,value pairs.
+     *
+     * @param PID Persistent identity string
+     * @param factoryPID Factory persistent identity string or {@code null}
+     * @param configProperties map of properties
+     * @param expectedStatus expected response status
+     * @return the location of the config
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public String editConfiguration(String PID, String factoryPID, Map<String, Object> configProperties, int... expectedStatus)
+            throws ClientException {
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("apply", "true");
+        builder.addParameter("action", "ajaxConfigManager");
+        // send factory PID if set
+        if (factoryPID != null) {
+            builder.addParameter("factoryPid", factoryPID);
+        }
+        // add properties to edit
+        StringBuilder propertyList = new StringBuilder("");
+        for (String propName : configProperties.keySet()) {
+            Object o = configProperties.get(propName);
+            if (o instanceof String) {
+                builder.addParameter(propName, (String)o);
+            } else if (o instanceof String[]) {
+                for (String s : (String[])o) {
+                    builder.addParameter(propName, s);
+                }
+            }
+            propertyList.append(propName).append(",");
+        }
+        // cut off the last comma
+        builder.addParameter("propertylist", propertyList.substring(0, propertyList.length() - 1));
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + PID, builder.build());
+        // check the returned status
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_MOVED_TEMPORARILY, expectedStatus));
+
+        Header[] locationHeader = resp.getHeaders("Location");
+        if (locationHeader!=null && locationHeader.length==1) {
+        	return locationHeader[0].getValue().substring(URL_CONFIGURATION.length()+1);
+        } else {
+        	return null;
+        }
+    }
+
+    /**
+     * Sets properties of a config referenced by its PID. the properties to be edited are passed as
+     * a map of property (name,value) pairs. The method waits until the configuration has been set.
+     *
+     * @param waitCount The number of maximum wait intervals of 500ms.
+     *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
+     * @param PID Persistent identity string
+     * @param factoryPID Factory persistent identity string or {@code null}
+     * @param configProperties map of properties
+     * @param expectedStatus expected response status
+     * @return the pid
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public String editConfigurationWithWait(int waitCount, String PID, String factoryPID, Map<String, Object> configProperties,
+                                            int... expectedStatus) throws ClientException, InterruptedException {
+        String pid = editConfiguration(PID, factoryPID, configProperties, expectedStatus);
+        getConfigurationWithWait(waitCount, pid);
+        return pid;
+    }
+
+    /**
+     * Delete the config referenced by the PID
+     *
+     * @param pid pid
+     * @param expectedStatus expected response status
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public void deleteConfiguration(String pid, int... expectedStatus) throws ClientException {
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("apply", "1");
+        builder.addParameter("delete", "1");
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + pid, builder.build());
+        // check the returned status         
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(200, expectedStatus));
+    }
+
+
+    class ConfigurationPoller extends AbstractPoller {
+
+        private final String pid;
+        int[] expectedStatus;
+        public Map<String, Object> config;
+
+        public ConfigurationPoller(long waitInterval, long waitCount, String pid, int... expectedStatus) {
+            super(waitInterval, waitCount);
+            this.pid = pid;
+            this.config = null;
+            this.expectedStatus = expectedStatus;
+        }
+
+        @Override
+        public boolean call() {
+            try {
+                config = getConfiguration(pid, expectedStatus);
+            } catch (ClientException e) {
+                LOG.warn("Couldn't get config " + pid, e);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean condition() {
+            return null != config;
+        }
+
+        public Map<String, Object> getConfig() {
+            return config;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
new file mode 100644
index 0000000..41ea39c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
@@ -0,0 +1,94 @@
+/*
+ * 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.testing.clients.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+/**
+ * <p>Allows saving and restoring the OSGiConfig to be used before and after altering OSGi configurations for tests</p>
+ * <p>See {@link InstanceConfig}</p>
+ */
+public class OsgiInstanceConfig implements InstanceConfig {
+
+    /**
+     * Number of retries for retrieving the current osgi config for save() and restore()
+     */
+    protected int waitCount = 20;
+
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
+    private final OsgiConsoleClient osgiClient;
+    private final String configPID;
+    private Map<String, Object> config;
+
+
+    /**
+     *
+     * @param client The Granite Client to be used internally
+     * @param configPID The PID for the OSGi configuration
+     * @param <T> The type of the Granite Client
+     * @throws ClientException if the client cannot be initialized
+     * @throws InstanceConfigException if the config cannot be saved
+     */
+    public <T extends SlingClient> OsgiInstanceConfig(T client, String configPID) throws ClientException, InstanceConfigException {
+        this.osgiClient = client.adaptTo(OsgiConsoleClient.class);
+        this.configPID = configPID;
+
+        // Save the configuration
+        save();
+    }
+
+    /**
+     * Save the current OSGi configuration for the PID defined in the constructor
+     *
+     * @throws InstanceConfigException if the config cannot be saved
+     */
+    public InstanceConfig save() throws InstanceConfigException {
+        try {
+            this.config = osgiClient.getConfigurationWithWait(waitCount, this.configPID);
+            LOG.info("Saved OSGi config for {}. It is currently this: {}", this.configPID, this.config);
+        } catch (ClientException e) {
+            throw new InstanceConfigException("Error getting config", e);
+        } catch (InterruptedException e) {
+            throw new InstanceConfigException("Saving configuration was interrupted ", e);
+        }
+        return this;
+    }
+
+    /**
+     * Restore the current OSGi configuration for the PID defined in the constructor
+     *
+     * @throws InstanceConfigException if the config cannot be restored
+     */
+    public InstanceConfig restore() throws InstanceConfigException {
+        try {
+            osgiClient.editConfigurationWithWait(waitCount, this.configPID, null, config);
+            LOG.info("restored OSGi config for {}. It is now this: {}", this.configPID, this.config);
+        } catch (ClientException e) {
+            throw new InstanceConfigException("Could not edit OSGi configuration", e);
+        } catch (InterruptedException e) {
+            throw new InstanceConfigException("Restoring configuration was interrupted", e);
+        }
+        return this;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
new file mode 100644
index 0000000..bbda9bc
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
@@ -0,0 +1,190 @@
+/*
+ * 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.testing.clients.osgi;
+
+
+import java.io.File;
+
+import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.content.FileBody;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.testing.tools.http.RequestBuilder;
+import org.apache.sling.testing.tools.http.RequestExecutor;
+import org.apache.sling.testing.tools.http.RetryingContentChecker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** HTTP Client for the Felix webconsole - simplistic for now */
+public class WebconsoleClient {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final RequestExecutor executor;
+    private final RequestBuilder builder;
+    private final String username;
+    private final String password;
+    
+    public static final String JSON_KEY_ID = "id";
+    public static final String JSON_KEY_VERSION = "version";
+    public static final String JSON_KEY_DATA = "data";
+    public static final String JSON_KEY_STATE = "state";
+    public static final String CONSOLE_BUNDLES_PATH = "/system/console/bundles";
+    
+    public WebconsoleClient(String slingServerUrl, String username, String password) {
+        this.builder = new RequestBuilder(slingServerUrl);
+        this.executor = new RequestExecutor(new DefaultHttpClient());
+        this.username = username;
+        this.password = password;
+    }
+    
+    public void uninstallBundle(String symbolicName, File f) throws Exception {
+        final long bundleId = getBundleId(symbolicName);
+        
+        log.info("Uninstalling bundle {} with bundleId {}", symbolicName, bundleId);
+
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("uninstall"));
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH+"/"+bundleId)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
+    public void installBundle(File f, boolean startBundle) throws Exception {
+        installBundle(f, startBundle, 0);
+    }
+    
+    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
+    public void installBundle(File f, boolean startBundle, int startLevel) throws Exception {
+        
+        // Setup request for Felix Webconsole bundle install
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("install"));
+        if(startBundle) {
+            entity.addPart("bundlestart", new StringBody("true"));
+        }
+        entity.addPart("bundlefile", new FileBody(f));
+        
+        if(startLevel > 0) {
+            entity.addPart("bundlestartlevel", new StringBody(String.valueOf(startLevel)));
+            log.info("Installing bundle {} at start level {}", f.getName(), startLevel);
+        } else {
+            log.info("Installing bundle {} at default start level", f.getName());
+        }
+        
+        // Console returns a 302 on success (and in a POST this
+        // is not handled automatically as per HTTP spec)
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(302);
+    }
+    
+    /** Check that specified bundle is installed - must be called
+     *  before other methods that take a symbolicName parameter, 
+     *  in case installBundle was just called and the actual 
+     *  installation hasn't happened yet. */
+    public void checkBundleInstalled(String symbolicName, int timeoutSeconds) {
+        final String path = getBundlePath(symbolicName, ".json");
+        new RetryingContentChecker(executor, builder, username, password).check(path, 200, timeoutSeconds, 500);
+    }
+    
+    private JSONObject getBundleData(String symbolicName) throws Exception {
+        // This returns a data structure like
+        // {"status":"Bundle information: 173 bundles in total - all 173 bundles active.","s":[173,171,2,0,0],"data":
+        //  [
+        //      {"id":0,"name":"System Bundle","fragment":false,"stateRaw":32,"state":"Active","version":"3.0.7","symbolicName":"org.apache.felix.framework","category":""},
+        //  ]}
+        final String path = getBundlePath(symbolicName, ".json");
+        final String content = executor.execute(
+                builder.buildGetRequest(path)
+                .withCredentials(username, password)
+        ).assertStatus(200)
+        .getContent();
+        
+        final JSONObject root = new JSONObject(content);
+        if(!root.has(JSON_KEY_DATA)) {
+            throw new Exception(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
+        }
+        final JSONArray data = root.getJSONArray(JSON_KEY_DATA);
+        if(data.length() < 1) {
+            throw new Exception(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
+        }
+        final JSONObject bundle = data.getJSONObject(0);
+        if(!bundle.has(JSON_KEY_STATE)) {
+            throw new Exception(path + ".data[0].state missing, JSON content=" + content);
+        }
+        return bundle;
+    }
+
+    /** Get bundle id */
+    public long getBundleId(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getLong(JSON_KEY_ID);
+    }
+    
+    /** Get bundle version **/
+    public String getBundleVersion(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getString(JSON_KEY_VERSION);
+    }
+    
+    /** Get specified bundle state */
+    public String getBundleState(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getString(JSON_KEY_STATE);
+    }
+    
+    /** Start specified bundle */
+    public void startBundle(String symbolicName) throws Exception {
+        // To start the bundle we POST action=start to its URL
+        final String path = getBundlePath(symbolicName, null);
+        log.info("Starting bundle {} via {}", symbolicName, path);
+        
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("start"));
+        executor.execute(
+                builder.buildPostRequest(path)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+    private String getBundlePath(String symbolicName, String extension) {
+        return CONSOLE_BUNDLES_PATH + "/" + symbolicName 
+        + (extension == null ? "" : extension);
+    }
+
+    /** Calls PackageAdmin.refreshPackages to enforce re-wiring of all bundles. */
+    public void refreshPackages() throws Exception {
+        log.info("Refresh packages.");
+
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action", new StringBody("refreshPackages"));
+
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
new file mode 100644
index 0000000..3deee0a
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.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.
+ */
+/**
+ * OSGI testing tools.
+ */
+@aQute.bnd.annotation.Version("1.0.0")
+package org.apache.sling.testing.clients.osgi;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
new file mode 100644
index 0000000..ec6c85a
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java b/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java
new file mode 100644
index 0000000..85822cc
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.testing.clients.util;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper for creating Entity objects for POST requests.
+ */
+public class FormEntityBuilder {
+    public final static String DEFAULT_ENCODING = "UTF-8";
+
+    private final List<NameValuePair> params;
+    private String encoding;
+
+    public static FormEntityBuilder create() {
+        return new FormEntityBuilder();
+    }
+
+    FormEntityBuilder() {
+        params = new ArrayList<NameValuePair>();
+        encoding = DEFAULT_ENCODING;
+    }
+
+    public FormEntityBuilder addAllParameters(Map<String, String> parameters) {
+        if (parameters != null) {
+            for (String key : parameters.keySet()) {
+                addParameter(key, parameters.get(key));
+            }
+        }
+
+        return this;
+    }
+
+    public FormEntityBuilder addAllParameters(List<NameValuePair> parameters) {
+        if (parameters != null) {
+            params.addAll(parameters);
+        }
+
+        return this;
+    }
+
+    public FormEntityBuilder addParameter(String name, String value) {
+        params.add(new BasicNameValuePair(name, value));
+        return this;
+    }
+
+    public FormEntityBuilder setEncoding(String encoding) {
+        this.encoding = encoding;
+        return this;
+    }
+
+    public UrlEncodedFormEntity build() {
+        try {
+            return new UrlEncodedFormEntity(params, encoding);
+        } catch (UnsupportedEncodingException ue) {
+            throw new Error("Unexpected UnsupportedEncodingException", ue);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java b/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
new file mode 100644
index 0000000..c845b28
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
@@ -0,0 +1,183 @@
+/*
+ * 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.testing.clients.util;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingHttpResponse;
+
+
+import java.net.URI;
+
+
+public class HttpUtils {
+
+    /**
+     * Verify expected status and dump response in case expected status is not returned.
+     * Warning! It will try to consume the entity in case of error
+     *
+     * @param response       The Sling HTTP response
+     * @param expectedStatus List of acceptable HTTP Statuses
+     * @throws ClientException if status is not expected
+     */
+    public static void verifyHttpStatus(SlingHttpResponse response, int... expectedStatus) throws ClientException {
+        if (!checkStatus(response, expectedStatus)) {
+            throwError(response, buildDefaultErrorMessage(response), expectedStatus);
+        }
+    }
+
+    /**
+     * Verify expected status and show error message in case expected status is not returned.
+     *
+     * @param response       The SlingHttpResponse of an executed request.
+     * @param errorMessage   error message; if {@code null}, errorMessage is extracted from response
+     * @param expectedStatus List of acceptable HTTP Statuses
+     * @throws ClientException if status is not expected
+     */
+    public static void verifyHttpStatus(HttpResponse response, String errorMessage, int... expectedStatus)
+            throws ClientException {
+        if (!checkStatus(response, expectedStatus)) {
+            throwError(response, errorMessage, expectedStatus);
+        }
+    }
+
+    private static boolean checkStatus(HttpResponse response, int... expectedStatus)
+            throws ClientException {
+
+        // if no HttpResponse was given
+        if (response == null) {
+            throw new NullPointerException("The response is null!");
+        }
+
+        // if no expected statuses are given
+        if (expectedStatus == null || expectedStatus.length == 0) {
+            throw new IllegalArgumentException("At least one expected HTTP Status must be set!");
+        }
+
+        // get the returned HTTP Status
+        int givenStatus = getHttpStatus(response);
+
+        // check if it matches with an expected one
+        for (int expected : expectedStatus) {
+            if (givenStatus == expected) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean throwError(HttpResponse response, String errorMessage, int... expectedStatus)
+            throws ClientException {
+        // build error message
+        String errorMsg = "Expected HTTP Status: ";
+        for (int expected : expectedStatus) {
+            errorMsg += expected + " ";
+        }
+
+        // get the returned HTTP Status
+        int givenStatus = getHttpStatus(response);
+
+        errorMsg += ". Instead " + givenStatus + " was returned!\n";
+        if (errorMessage != null) {
+            errorMsg += errorMessage;
+        }
+
+        // throw the exception
+        throw new ClientException(errorMsg);
+    }
+
+
+    /**
+     * Build default error message
+     *
+     * @param resp The response of a sling request
+     * @return default error message
+     */
+    public static String buildDefaultErrorMessage(SlingHttpResponse resp) {
+
+        String content = resp.getContent();
+
+        // if no response content is available
+        if (content == null) return "";
+        String errorMsg = resp.getSlingMessage();
+
+        errorMsg = (errorMsg == null || errorMsg.length() == 0)
+                // any other returned content
+                ? " Response Content:\n" + content
+                // response message from sling response
+                : "Error Message: \n" + errorMsg;
+
+        return errorMsg;
+    }
+
+    /**
+     * Get HTTP Status of the response.
+     *
+     * @param response The RequestExecutor of an executed request.
+     * @return The HTTP Status of the response
+     * @throws ClientException never (kept for uniformity)
+     */
+    public static int getHttpStatus(HttpResponse response) throws ClientException {
+        return response.getStatusLine().getStatusCode();
+    }
+
+    /**
+     * Get the first 'Location' header and verify it's a valid URI.
+     *
+     * @param response HttpResponse the http response
+     * @return the location path
+     * @throws ClientException never (kept for uniformity)
+     */
+    public static String getLocationHeader(HttpResponse response) throws ClientException {
+        if (response == null) throw new ClientException("Response must not be null!");
+
+        String locationPath = null;
+        Header locationHeader = response.getFirstHeader("Location");
+        if (locationHeader != null) {
+            String location = locationHeader.getValue();
+            URI locationURI = URI.create(location);
+            locationPath = locationURI.getPath();
+        }
+
+        if (locationPath == null) {
+            throw new ClientException("not able to determine location path");
+        }
+        return locationPath;
+    }
+
+    /**
+     * Check if expected status is in range
+     *
+     * @param response the http response
+     * @param range    the http status range
+     * @return true if response is in range
+     */
+    public static boolean isInHttpStatusRange(HttpResponse response, int range) {
+        return range == response.getStatusLine().getStatusCode() / 100 * 100;
+    }
+
+    public static int[] getExpectedStatus(int defaultStatus, int... expectedStatus) {
+        if (expectedStatus == null || expectedStatus.length == 0) {
+            expectedStatus = new int[]{defaultStatus};
+        }
+        return expectedStatus;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java b/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java
new file mode 100644
index 0000000..36361ef
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.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.testing.clients.util;
+
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.sling.testing.clients.ClientException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * If we want to upload a file that is a resource in a jar file, the http client expects a content length.
+ */
+public class InputStreamBodyWithLength extends InputStreamBody {
+    private long streamLength;
+
+    public InputStreamBodyWithLength(String resourcePath, String contentType, String fileName) throws ClientException {
+        super(ResourceUtil.getResourceAsStream(resourcePath), ContentType.create(contentType), fileName);
+        this.streamLength = getResourceStreamLength(resourcePath);
+    }
+
+    @Override
+    public long getContentLength() {
+        return streamLength;
+    }
+
+    /**
+     * Returns the length of a resource (which is needed for the InputStreamBody
+     * to work. Can't currently think of a better solution than going through
+     * the resource stream and count.
+     *
+     * @param resourcePath path to the file
+     * @return the size of the resource
+     */
+    private static long getResourceStreamLength(String resourcePath) throws ClientException {
+        int streamLength = 0;
+        InputStream stream = ResourceUtil.getResourceAsStream(resourcePath);
+        try {
+            for (int avail = stream.available(); avail > 0; avail = stream.available()) {
+                streamLength += avail;
+                stream.skip(avail);
+            }
+        } catch (IOException e) {
+            throw new ClientException("Could not read " + resourcePath + "!", e);
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                throw new ClientException("Could not close Inputstream for " + resourcePath + "!", e);
+            }
+        }
+        return streamLength;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java b/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java
new file mode 100644
index 0000000..e64ac8d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.ObjectMapper;
+
+import java.io.IOException;
+
+public class JsonUtils {
+    /**
+     * Get {@link JsonNode} from a a String containing JSON.
+     *
+     * @param jsonString A string containing JSON
+     * @return A {@link JsonNode} that is the root node of the JSON structure.
+     * @throws ClientException if error occurs while reading json string
+     */
+    public static JsonNode getJsonNodeFromString(String jsonString) throws ClientException {
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            return mapper.readTree(jsonString);
+        } catch (JsonProcessingException e) {
+            throw new ClientException("Could not read json file.", e);
+        } catch (IOException e) {
+            throw new ClientException("Could not read json node.", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java b/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java
new file mode 100644
index 0000000..a295a85
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.testing.clients.util;
+
+import java.net.ServerSocket;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PortAllocator {
+
+    private static Set<Integer> allocatedPorts;
+
+    static {
+        allocatedPorts = new HashSet<Integer>();
+    }
+
+    public Integer allocatePort() {
+        while (true) {
+            int port = tryAllocation();
+
+            boolean portAdded = checkAndAddPort(port);
+
+            if (portAdded) {
+                return port;
+            }
+        }
+    }
+
+    private int tryAllocation() {
+        try {
+            ServerSocket serverSocket = new ServerSocket(0);
+            int port = serverSocket.getLocalPort();
+            serverSocket.close();
+            return port;
+        } catch (Exception e) {
+            throw new RuntimeException("Can't allocate a port");
+        }
+    }
+
+    private synchronized boolean checkAndAddPort(int port) {
+        return allocatedPorts.add(port);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java b/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java
new file mode 100644
index 0000000..6b88c53
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java
@@ -0,0 +1,66 @@
+/*
+ * 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.testing.clients.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class ResourceUtil {
+
+    /**
+     * We must get the Resource as a stream from the ContextClassLoader and not from the normal classLoader
+     * acquired by using getClass.getClassLoader, since we must be able to load resources from different threads
+     * e.g. running in ant.
+     *
+     * @param resourcePath path to the resource
+     * @return resource as InputStream
+     */
+    public static InputStream getResourceAsStream(String resourcePath) {
+        return Thread.currentThread().getContextClassLoader().getClass().getResourceAsStream(resourcePath);
+    }
+
+    /**
+     * Helper method to read a resource from class using {@link Class#getResourceAsStream(String)}
+     * and convert into a String.
+     *
+     * @param resource The resource to read.
+     * @return The requested resource as String, resolved using
+     *         {@link Class#getResourceAsStream(String)}, or {@code null}
+     *         if the requested resource cannot be resolved for some reason
+     * @throws IOException if the Resource Stream cannot be read
+     */
+    public static String readResourceAsString(String resource) throws IOException {
+        InputStream resourceAsStream = ResourceUtil.getResourceAsStream(resource);
+        if (resourceAsStream != null) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            try {
+                BufferedReader reader = new BufferedReader(new InputStreamReader(resourceAsStream, "UTF-8"));
+                while ((line = reader.readLine()) != null) {
+                    sb.append(line).append("\n");
+                }
+            } finally {
+                resourceAsStream.close();
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java b/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java
new file mode 100644
index 0000000..fbf6be2
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public class SlingParameter {
+
+    private String typeHint = null;
+    private boolean delete = false;
+
+    String parameterName;
+    private String[] values = null;
+    private boolean multiple = false;
+
+    public SlingParameter(String parameterName) {
+        if (parameterName == null || parameterName.length() == 0) {
+            throw new IllegalArgumentException("parameterName must not be null or empty");
+        }
+        this.parameterName = parameterName;
+    }
+
+    public SlingParameter value(String value) {
+        if (value != null) {
+            this.values(new String[]{value});
+        } else {
+            this.values(new String[]{});
+        }
+        return this;
+    }
+
+    public SlingParameter values(String[] values) {
+        if (values == null) {
+            this.values = new String[]{};
+        } else {
+            this.values = values;
+        }
+        return this;
+    }
+
+    public SlingParameter typeHint(String typeHint) {
+        this.typeHint = typeHint;
+        return this;
+    }
+
+    public SlingParameter delete() {
+        this.delete = true;
+        return this;
+    }
+
+    public SlingParameter multiple() {
+        this.multiple = true;
+        return this;
+    }
+
+    public List<NameValuePair> toNameValuePairs() {
+        List<NameValuePair> parameters = new ArrayList<NameValuePair>();
+
+        if (multiple) {
+            for (String value : values) {
+                parameters.add(new BasicNameValuePair(parameterName, value));
+            }
+        } else if (values != null && values.length == 1) {
+            parameters.add(new BasicNameValuePair(parameterName, values[0]));
+        } else if (values != null && values.length > 1) {
+            // TODO not sure about the proper format of the values in this case?
+            // For now, only take the first one.
+            parameters.add(new BasicNameValuePair(parameterName, values[0]));
+        } else {
+            parameters.add(new BasicNameValuePair(parameterName, null));
+        }
+
+        // add @TypeHint suffix
+        if (typeHint != null) {
+            String parameter = parameterName + "@TypeHint";
+            parameters.add(new BasicNameValuePair(parameter, typeHint));
+        }
+
+        // add @Delete suffix
+        if (delete) {
+            String parameter = parameterName + "@Delete";
+            parameters.add(new BasicNameValuePair(parameter, "true"));
+        }
+
+        return parameters;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java b/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java
new file mode 100644
index 0000000..e47f7ab
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.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.testing.clients.util;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class URLParameterBuilder {
+
+    public final static String DEFAULT_ENCODING = "UTF-8";
+
+    private List<NameValuePair> params;
+    private String encoding;
+
+    public static URLParameterBuilder create() {
+        return new URLParameterBuilder();
+    }
+
+    URLParameterBuilder() {
+        params = new ArrayList<NameValuePair>();
+        encoding = DEFAULT_ENCODING;
+    }
+
+    public URLParameterBuilder add(String name, String value) {
+        params.add(new BasicNameValuePair(name, value));
+        return this;
+    }
+
+    public URLParameterBuilder add(NameValuePair pair) {
+        params.add(pair);
+        return this;
+    }
+
+    public URLParameterBuilder add(List<NameValuePair> list) {
+        params.addAll(list);
+        return this;
+    }
+
+    public URLParameterBuilder add(String name, String[] values) {
+        for (String value : values) this.add(name, value);
+        return this;
+    }
+
+    public URLParameterBuilder setEncoding(String encoding) {
+        this.encoding = encoding;
+        return this;
+    }
+
+    /**
+     * Build the URL parameters
+     *
+     * @return The URL parameters string without the leading question mark.
+     */
+    public String getURLParameters() {
+        return URLEncodedUtils.format(params, encoding);
+    }
+
+    public List<NameValuePair> getList() {
+        return params;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java b/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java
new file mode 100644
index 0000000..a80f97e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java
@@ -0,0 +1,67 @@
+/*
+ * 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.testing.clients.util;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/** Generate unique paths, for tests isolation */
+public class UniquePaths {
+
+    private static long startTime = System.currentTimeMillis();
+    private static AtomicLong counter = new AtomicLong();
+    public final static String SEP = "_";
+    public final static String U_PATTERN = "_UNIQ_";
+    
+    /**
+     * Return a unique path based on basePath
+     * @param nameReference The simple class name of that object is used as part of the 
+     *                      generated unique ID
+     * @param basePath All occurrences of {@link UniquePaths#U_PATTERN} in basePath are replaced by the generated
+     *                 unique ID. If $U$ is not found in basePath, unique ID is added at its end.
+     * @return path with a unique value for each call.
+     */
+    public static String get(Object nameReference, String basePath) {
+        if(basePath == null) {
+            basePath = "";
+        }
+        
+        final StringBuilder sb = new StringBuilder();
+        sb.append(nameReference.getClass().getSimpleName());
+        sb.append(SEP);
+        sb.append(startTime);
+        sb.append(SEP);
+        sb.append(counter.incrementAndGet());
+        
+        if(basePath.contains(U_PATTERN)) {
+            return basePath.replaceAll(U_PATTERN, sb.toString());
+        } else {
+            return basePath + sb.toString();
+        }
+    }
+    
+    /**
+     * Get a unique ID with no base path
+     *
+     * @param nameReference The simple class name of that object is used as part of the
+     *                      generated unique ID
+     * @return path with a unique value for each call
+     */
+    public static String get(Object nameReference) {
+        return get(nameReference, null);
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java b/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java
new file mode 100644
index 0000000..852cb12
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java
@@ -0,0 +1,137 @@
+/*
+ * 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.testing.clients.util;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.sling.xss.XSSAPI;
+import org.apache.sling.xss.impl.XSSAPIImpl;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * Basic class for XSS Testing
+ * The reliability of these methods are not critical
+ */
+public class XSSUtils {
+
+    /**
+     * Use to ensure that HTTP query strings are in proper form, by escaping
+     * special characters such as spaces.
+     *
+     * @param urlString the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeUrl(String urlString) {
+        try {
+            return URLEncoder.encode(urlString, "UTF-8");
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("UTF-8 not supported", ex);
+        }
+    }
+
+    /**
+     * Use to encapsulate old-style escaping of HTML (using StringEscapeUtils).
+     * NB: newer code uses XSSAPI (based on OWASP's ESAPI).
+     *
+     * @param htmlString the string to be escaped
+     * @return the escaped string
+     */
+    public static String escapeHtml(String htmlString) {
+        return StringEscapeUtils.escapeHtml4(htmlString);
+    }
+
+    /**
+     * Use to encapsulate old-style escaping of XML (with JSTL encoding rules).
+     * NB: newer code uses XSSAPI (based on OWASP's ESAPI).
+     *
+     * @param xmlString the string to be escaped
+     * @return the escaped string
+     */
+    public static String escapeXml(String xmlString) {
+        String xssString = xmlString;
+        if (xmlString != null) {
+            xssString = xssString.replace(";", "&#x3b;");
+            xssString = xssString.replace(" ", "&#x20;");
+            xssString = xssString.replace("'", "&#x27;");
+            xssString = xssString.replace("\"", "&quot;");
+            xssString = xssString.replace(">", "&gt;");
+            xssString = xssString.replace("<", "&lt;");
+            xssString = xssString.replace("/", "&#x2f;");
+            xssString = xssString.replace("(", "&#x28;");
+            xssString = xssString.replace(")", "&#x29;");
+            xssString = xssString.replace(":", "&#x3a;");
+        }
+        return xssString;
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for HTML element content.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForHTML(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForHTML(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for HTML attribute values.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForHTMLAttr(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForHTMLAttr(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for XML element content.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForXML(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForXML(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for XML attribute values.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForXMLAttr(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForXMLAttr(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for JavaScript strings.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForJSString(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForJSString(source);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
new file mode 100644
index 0000000..8af55ac
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.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.testing.clients.util.config;
+
+/**
+ * Allows saving and restoring an instance configuration.
+ * Implementations define the behaviour of save() and restore()
+ */
+public interface InstanceConfig {
+
+    /**
+     * Saves the current status of the configuration
+     *
+     * @return this
+     * @throws InstanceConfigException if saving the configuration fails
+     */
+    public InstanceConfig save() throws InstanceConfigException;
+
+    /**
+     * Restores the saved status of the configuration
+     *
+     * @return this
+     * @throws InstanceConfigException if restoring the configuration fails
+     */
+    public InstanceConfig restore() throws InstanceConfigException;
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java
new file mode 100644
index 0000000..b473c86
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java
@@ -0,0 +1,25 @@
+/*
+ * 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.testing.clients.util.config;
+
+import java.util.Collection;
+
+/**
+ * A cache for different {@link InstanceConfig} objects
+ */
+public interface InstanceConfigCache  extends InstanceConfig, Collection<InstanceConfig> {
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java
new file mode 100644
index 0000000..1c2597e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.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.testing.clients.util.config;
+
+public class InstanceConfigException extends Exception {
+
+    public InstanceConfigException(Exception e) {
+        super(e);
+    }
+
+    public InstanceConfigException() {
+    }
+
+    public InstanceConfigException(String message) {
+        super(message);
+    }
+
+    public InstanceConfigException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InstanceConfigException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java
new file mode 100644
index 0000000..4801ad3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config.impl;
+
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EmptyInstanceConfig implements InstanceConfig {
+    private static final Logger LOG = LoggerFactory.getLogger(EmptyInstanceConfig.class);
+
+    public InstanceConfig save() throws InstanceConfigException {
+        LOG.debug("Saved nothing");
+        return this;
+    }
+
+    public InstanceConfig restore() throws InstanceConfigException {
+        LOG.debug("Restored nothing");
+        return this;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
new file mode 100644
index 0000000..4bae77f
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.testing.clients.util.config.impl;
+
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigCache;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public class InstanceConfigCacheImpl implements InstanceConfigCache {
+    List<InstanceConfig> configs;
+
+    public InstanceConfigCacheImpl(List<InstanceConfig> configs) {
+        this.configs = configs;
+    }
+
+    public InstanceConfigCacheImpl() {
+        this.configs = new ArrayList<InstanceConfig>();
+    }
+
+    @Override
+    public int size() {
+        return configs.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return configs.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return configs.contains(o);
+    }
+
+    @Override
+    public Iterator<InstanceConfig> iterator() {
+        return configs.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return configs.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return configs.toArray(a);
+    }
+
+    @Override
+    public boolean add(InstanceConfig instanceConfig) {
+        return configs.add(instanceConfig);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return configs.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return configs.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends InstanceConfig> c) {
+        return configs.addAll(c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return configs.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return configs.retainAll(c);
+    }
+
+    @Override
+    public void clear() {
+       configs.clear();
+    }
+
+
+    @Override
+    public InstanceConfig save() throws InstanceConfigException {
+        for (InstanceConfig ic : configs) {
+            ic.save();
+        }
+        return this;
+    }
+
+    @Override
+    public InstanceConfig restore() throws InstanceConfigException {
+        for (InstanceConfig ic : configs) {
+            ic.restore();
+        }
+        return this;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
new file mode 100644
index 0000000..59d0aaa
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util.config;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
new file mode 100644
index 0000000..57c34c3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.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.testing.clients.util.poller;
+
+public abstract class AbstractPoller  implements Poller {
+
+    private final long waitInterval;
+    private final long waitCount;
+
+    /**
+     * Convenience method to execute a generic call and do polling until a condition is met
+     * The user must implement the {@link Poller#call()} and {@link Poller#condition()} methods
+     * @param waitInterval Number of milliseconds to wait between polls
+     * @param waitCount Number of wait intervals
+     */
+    public AbstractPoller(long waitInterval, long waitCount) {
+        this.waitInterval = waitInterval;
+        this.waitCount = waitCount;
+    }
+
+    /**
+     * Calls the {@link Poller#call()} once and then calls {@link Poller#condition()} until it returns true
+     * The method waits AbstractPoller#waitInterval milliseconds between calls to {@link Poller#condition()}
+     * A maximum of AbstractPoller#waitCount intervals are checked
+     * @return true if the condition is met after waiting a maximum of AbstractPoller#waitCount intervals, false otherwise
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public boolean callAndWait() throws InterruptedException {
+        if (!call()) return false;
+        for (int i=0; i<waitCount; i++) {
+            if (condition()) return true;
+            Thread.sleep(waitInterval);
+        }
+        return false;
+    }
+
+    /**
+     * Calls the @see: Poller#call() and then calls {@link Poller#condition()} until it returns true
+     * The Poller#call() method is called in each wait interval, before the Poller#condition().
+     * The method waits AbstractPoller#waitInterval milliseconds between calls to {@link Poller#condition()}
+     * A maximum of AbstractPoller#waitCount intervals are checked
+     * @return true if the condition is met after waiting a maximum of AbstractPoller#waitCount intervals, false otherwise
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public boolean callUntilCondition() throws InterruptedException {
+        if (!call()) return false;
+        if (condition()) return true;
+        for (int i = 0; i < waitCount; i++) {
+            Thread.sleep(waitInterval);
+            if (!call()) return false;
+            if (condition()) return true;
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
new file mode 100644
index 0000000..e772edb
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.poller;
+
+/**
+ * Abstract Poller interface.
+ * Provides simple methods to implement custom pollers
+ */
+public interface Poller {
+    boolean call();
+    boolean condition();
+    boolean callAndWait() throws InterruptedException;
+    boolean callUntilCondition() throws InterruptedException;
+}
diff --git a/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java b/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java
new file mode 100644
index 0000000..9fe0086
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java
@@ -0,0 +1,77 @@
+/*
+ * 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.testing.timeouts;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Return timeout values that can be multiplied by a configurable
+ *  factor. Useful to cope with slower integration testing systems:
+ *  use timeout constants in your code that work for usual development
+ *  systems, and set a multiplier when running on a slower system.
+ */
+public class TimeoutsProvider {
+    private static final Logger log = LoggerFactory.getLogger(TimeoutsProvider.class);
+    public static final String PROP_TIMEOUT_MULTIPLIER = "sling.testing.timeout.multiplier";
+    private static float timeoutFactor = -1;
+    private static TimeoutsProvider INSTANCE;
+    
+    private TimeoutsProvider() {
+        if(timeoutFactor < 0) {
+            timeoutFactor = 1;
+            final String str = System.getProperty(PROP_TIMEOUT_MULTIPLIER);
+            if(str != null) {
+                try {
+                    timeoutFactor = Float.valueOf(str.trim());
+                    log.info("Timeout factor set to {} from system property {}", 
+                            timeoutFactor, PROP_TIMEOUT_MULTIPLIER);
+                } catch(NumberFormatException nfe) {
+                    throw new IllegalStateException("Invalid timeout factor: " + PROP_TIMEOUT_MULTIPLIER + "=" + str);
+                }
+            }
+        }
+    }
+    
+    public static TimeoutsProvider getInstance() {
+        if(INSTANCE == null) {
+            synchronized (TimeoutsProvider.class) {
+                INSTANCE = new TimeoutsProvider();
+            }
+        }
+        return INSTANCE;
+    }
+    
+    public long getTimeout(long nomimalValue) {
+        final long result = (long)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    public int getTimeout(int nomimalValue) {
+        final int result = (int)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    /** Get timeout from a system property, with default value */
+    public int getTimeout(String systemPropertyName, int defaultNominalValue) {
+        int result = defaultNominalValue;
+        final String str = System.getProperty(systemPropertyName);
+        if(str != null) {
+            result = Integer.parseInt(str);
+        }
+        return getTimeout(result);
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java
new file mode 100644
index 0000000..64f4c73
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.testing;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetPathTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, input: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                {"http://HOST",              "http://HOST/page.html",             "/page.html"},
+                {"http://HOST",              "http://HOST/my/page.html",          "/my/page.html"},
+                {"http://HOST",              "http://HOST/my/",                   "/my/"},
+                {"http://HOST",              "http://HOST/my",                    "/my"},
+                {"http://HOST",              "http://HOST/",                      "/"},
+                {"http://HOST",              "http://HOST",                       "/"},
+                {"http://HOST",              "/page.html",                        "/page.html"},
+                {"http://HOST",              "/my/page.html",                     "/my/page.html"},
+                {"http://HOST",              "/my/",                              "/my/"},
+                {"http://HOST",              "/",                                 "/"},
+                {"http://HOST",              "page.html",                         "/page.html"},
+                {"http://HOST",              "my/page.html",                      "/my/page.html"},
+                {"http://HOST",              "my",                                "/my"},
+                {"http://HOST",              "",                                  "/"},
+
+                {"http://HOST:4502",         "http://HOST:4502/page.html",        "/page.html"},
+                {"http://HOST:4502",         "http://HOST:4502/my/page.html",     "/my/page.html"},
+                {"http://HOST:4502",         "http://HOST:4502/my/",              "/my/"},
+                {"http://HOST:4502",         "http://HOST:4502/my",               "/my"},
+                {"http://HOST:4502",         "http://HOST:4502/",                 "/"},
+                {"http://HOST:4502",         "http://HOST:4502",                  "/"},
+                {"http://HOST:4502",         "/page.html",                        "/page.html"},
+                {"http://HOST:4502",         "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502",         "/my/",                              "/my/"},
+                {"http://HOST:4502",         "/my",                               "/my"},
+                {"http://HOST:4502",         "/",                                 "/"},
+                {"http://HOST:4502",         "page.html",                         "/page.html"},
+                {"http://HOST:4502",         "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502",         "my/",                               "/my/"},
+                {"http://HOST:4502",         "my",                                "/my"},
+                {"http://HOST:4502",         "",                                  "/"},
+
+                {"http://HOST:4502/",        "http://HOST:4502/page.html",        "/page.html"},
+                {"http://HOST:4502/",        "http://HOST:4502/my/page.html",     "/my/page.html"},
+                {"http://HOST:4502/",        "http://HOST:4502/my/",              "/my/"},
+                {"http://HOST:4502/",        "http://HOST:4502/my",               "/my"},
+                {"http://HOST:4502/",        "http://HOST:4502/",                 "/"},
+                {"http://HOST:4502/",        "http://HOST:4502",                  "/"},
+                {"http://HOST:4502/",        "/page.html",                        "/page.html"},
+                {"http://HOST:4502/",        "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/",        "/my/",                              "/my/"},
+                {"http://HOST:4502/",        "/my",                               "/my"},
+                {"http://HOST:4502/",        "/",                                 "/"},
+                {"http://HOST:4502/",        "page.html",                         "/page.html"},
+                {"http://HOST:4502/",        "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/",        "my/",                               "/my/"},
+                {"http://HOST:4502/",        "my",                                "/my"},
+                {"http://HOST:4502/",        "",                                  "/"},
+
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/page.html",    "/page.html"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my/page.html", "/my/page.html"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my/",          "/my/"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my",           "/my"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/",             "/"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX",              "/"},
+                {"http://HOST:4502/CTX",     "/CTX",                              "/"},
+                {"http://HOST:4502/CTX",     "/CTX/",                             "/"},
+                {"http://HOST:4502/CTX",     "/CTX/page.html",                    "/page.html"},
+                {"http://HOST:4502/CTX",     "/page.html",                        "/page.html"},
+                {"http://HOST:4502/CTX",     "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/CTX",     "/my/",                              "/my/"},
+                {"http://HOST:4502/CTX",     "/my",                               "/my"},
+                {"http://HOST:4502/CTX",     "/",                                 "/"},
+                {"http://HOST:4502/CTX",     "CTX",                               "/"},
+                {"http://HOST:4502/CTX",     "CTX/",                              "/"},
+                {"http://HOST:4502/CTX",     "CTX/page.html",                     "/page.html"},
+                {"http://HOST:4502/CTX",     "page.html",                         "/page.html"},
+                {"http://HOST:4502/CTX",     "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/CTX",     "my/",                               "/my/"},
+                {"http://HOST:4502/CTX",     "my",                                "/my"},
+                {"http://HOST:4502/CTX",     "",                                  "/"},
+
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/page.html",    "/page.html"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/page.html", "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/",          "/my/"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my",           "/my"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/",             "/"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX",              "/"},
+                {"http://HOST:4502/CTX/",    "/CTX",                              "/"},
+                {"http://HOST:4502/CTX/",    "/CTX/",                             "/"},
+                {"http://HOST:4502/CTX/",    "/CTX/page.html",                    "/page.html"},
+                {"http://HOST:4502/CTX/",    "/page.html",                        "/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/",                              "/my/"},
+                {"http://HOST:4502/CTX/",    "/my",                               "/my"},
+                {"http://HOST:4502/CTX/",    "/",                                 "/"},
+                {"http://HOST:4502/CTX/",    "CTX",                               "/"},
+                {"http://HOST:4502/CTX/",    "CTX/",                              "/"},
+                {"http://HOST:4502/CTX/",    "CTX/page.html",                     "/page.html"},
+                {"http://HOST:4502/CTX/",    "page.html",                         "/page.html"},
+                {"http://HOST:4502/CTX/",    "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "my/",                               "/my/"},
+                {"http://HOST:4502/CTX/",    "my",                                "/my"},
+                {"http://HOST:4502/CTX/",    "",                                  "/"},
+
+                {"http://HOST:4502/CTX/",    "http://www.google.com",             "http://www.google.com"},
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String inputUri;
+
+    @Parameterized.Parameter(value = 2)
+    public String expectedPath;
+
+    @Test
+    public void testGetPath() throws ClientException, URISyntaxException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals(URI.create(expectedPath), c.getPath(inputUri));
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java
new file mode 100644
index 0000000..bde2c13
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.testing;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetServerUrlTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, path: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                {"http://HOST",             "http://HOST/"},
+                {"http://HOST:4502",        "http://HOST:4502/"},
+                {"http://HOST:4502/",       "http://HOST:4502/"},
+                {"http://HOST:4502/CTX",    "http://HOST:4502/CTX/"},
+                {"http://HOST:4502/CTX/",   "http://HOST:4502/CTX/"},
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String expectedUrl;
+
+    @Test
+    public void testGetUrl() throws ClientException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals("", URI.create(expectedUrl), c.getUrl());
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java
new file mode 100644
index 0000000..665b595
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.testing;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetUrlTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, path: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                // Server URL with no port
+                {"http://HOST",              "/page.html",            "http://HOST/page.html"},
+                {"http://HOST",              "/my/page.html",         "http://HOST/my/page.html"},
+                {"http://HOST",              "/my/",                  "http://HOST/my/"},
+                {"http://HOST",              "/my",                   "http://HOST/my"},
+                {"http://HOST",              "/",                     "http://HOST/"},
+
+                {"http://HOST",              "page.html",             "http://HOST/page.html"},
+                {"http://HOST",              "my/page.html",          "http://HOST/my/page.html"},
+                {"http://HOST",              "my/",                   "http://HOST/my/"},
+                {"http://HOST",              "my",                    "http://HOST/my"},
+                {"http://HOST",              "",                      "http://HOST/"},
+
+                // Server URL with with port
+                {"http://HOST:4502",         "/page.html",            "http://HOST:4502/page.html"},
+                {"http://HOST:4502",         "/my/page.html",         "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502",         "/my/",                  "http://HOST:4502/my/"},
+                {"http://HOST:4502",         "/my",                   "http://HOST:4502/my"},
+                {"http://HOST:4502",         "/",                     "http://HOST:4502/"},
+
+                {"http://HOST:4502",         "page.html",             "http://HOST:4502/page.html"},
+                {"http://HOST:4502",         "my/page.html",          "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502",         "my/",                   "http://HOST:4502/my/"},
+                {"http://HOST:4502",         "my",                    "http://HOST:4502/my"},
+                {"http://HOST:4502",         "",                      "http://HOST:4502/"},
+
+                // Server URL with with port and trailing slash
+                {"http://HOST:4502/",        "/page.html",            "http://HOST:4502/page.html"},
+                {"http://HOST:4502/",        "/my/page.html",         "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502/",        "/my/",                  "http://HOST:4502/my/"},
+                {"http://HOST:4502/",        "/my",                   "http://HOST:4502/my"},
+                {"http://HOST:4502/",        "/",                     "http://HOST:4502/"},
+
+                {"http://HOST:4502/",        "page.html",             "http://HOST:4502/page.html"},
+                {"http://HOST:4502/",        "my/page.html",          "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502/",        "my/",                   "http://HOST:4502/my/"},
+                {"http://HOST:4502/",        "my",                    "http://HOST:4502/my"},
+                {"http://HOST:4502/",        "",                      "http://HOST:4502/"},
+
+                // Server URL with with port and context path (no trailing slash)
+                {"http://HOST:4502/CTX",     "/page.html",            "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX",     "/my/page.html",         "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX",     "/my/",                  "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX",     "/my",                   "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX",     "/",                     "http://HOST:4502/CTX/"},
+
+                {"http://HOST:4502/CTX",     "page.html",             "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX",     "my/page.html",          "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX",     "my/",                   "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX",     "my",                    "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX",     "",                      "http://HOST:4502/CTX/"},
+
+                // Server URL with with port and context path and trailing slash
+                {"http://HOST:4502/CTX/",    "/page.html",            "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/page.html",         "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/",                  "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX/",    "/my",                   "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX/",    "/",                     "http://HOST:4502/CTX/"},
+
+                {"http://HOST:4502/CTX/",    "page.html",             "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX/",    "my/page.html",          "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX/",    "my/",                   "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX/",    "my",                    "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX/",    "",                      "http://HOST:4502/CTX/"},
+
+                // External URLs
+                {"http://HOST:4502/CTX/",    "http://www.google.com", "http://www.google.com"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/page.html", "http://HOST:4502/CTX/my/page.html"},
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String inputPath;
+
+    @Parameterized.Parameter(value = 2)
+    public String expectedUrl;
+
+    @Test
+    public void testGetUrlWithParam() throws ClientException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals("", URI.create(expectedUrl), c.getUrl(inputPath));
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java b/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java
new file mode 100644
index 0000000..c44d238
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.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.testing;
+
+import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DelayRequestInterceptorTest {
+
+    @Test
+    public void testDelay() throws Exception {
+        DelayRequestInterceptor interceptor = new DelayRequestInterceptor(1000);
+        long before = System.currentTimeMillis();
+        interceptor.process(null, null);
+        long after = System.currentTimeMillis();
+        Assert.assertTrue(after - before >= 1000);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/util/UniquePathsTest.java b/src/test/java/org/apache/sling/testing/util/UniquePathsTest.java
new file mode 100644
index 0000000..c7e40e1
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/util/UniquePathsTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.testing.util;
+
+import org.apache.sling.testing.clients.util.UniquePaths;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.Assert.assertEquals;
+
+public class UniquePathsTest {
+
+    @Before
+    public void setup() throws Exception {
+        // Set known startTime and counter values for tests
+        {
+            final Field f = UniquePaths.class.getDeclaredField("startTime");
+            f.setAccessible(true);
+            f.set(UniquePaths.class, 1234L);
+        }
+        {
+            final Field f = UniquePaths.class.getDeclaredField("counter");
+            f.setAccessible(true);
+            f.set(UniquePaths.class, new AtomicLong(9362L));
+        }
+    }
+    
+    @Test
+    public void testNoUPattern() {
+        assertEquals("/tmp/UniquePathsTest_1234_9363", UniquePaths.get(this, "/tmp/"));
+        assertEquals("/bar/UniquePathsTest_1234_9364", UniquePaths.get(this, "/bar/"));
+    }
+    
+    @Test
+    public void testSingleUPattern() {
+        assertEquals("/tmp/UniquePathsTest_1234_9363/foo", UniquePaths.get(this, "/tmp/_UNIQ_/foo"));
+    }
+    
+    @Test
+    public void testMultipleUPattern() {
+        assertEquals(
+                "/tmp/UniquePathsTest_1234_9363/foo/UniquePathsTest_1234_9363.html", 
+                UniquePaths.get(this, "/tmp/_UNIQ_/foo/_UNIQ_.html"));
+    }
+    
+    @Test
+    public void testNullPattern() {
+        assertEquals(
+                "UniquePathsTest_1234_9363", 
+                UniquePaths.get(this, null));
+    }
+    
+    @Test
+    public void testNoPattern() {
+        assertEquals(
+                "UniquePathsTest_1234_9363", 
+                UniquePaths.get(this));
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/util/poller/AbstractPollerTest.java b/src/test/java/org/apache/sling/testing/util/poller/AbstractPollerTest.java
new file mode 100644
index 0000000..c7681a3
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/util/poller/AbstractPollerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.testing.util.poller;
+
+import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AbstractPollerTest {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractPollerTest.class);
+
+    @Test
+    public void testCallAndWaitSuccess() throws InterruptedException {
+        AbstractPoller poller = new AbstractPoller(100, 5) {
+            int callNumber = 0;
+
+            @Override
+            public boolean call() {
+                return true;
+            }
+
+            @Override
+            public boolean condition() {
+                callNumber += 1;
+                LOG.debug("Call nr " + callNumber);
+                if (callNumber == 4) {
+                    return true;
+                }
+                return false;
+            }
+        };
+        Assert.assertTrue(poller.callAndWait());
+    }
+
+    @Test
+    public void testCallAndWaitFailure() throws InterruptedException {
+        AbstractPoller poller = new AbstractPoller(100, 5) {
+            @Override
+            public boolean call() {
+                return true;
+            }
+
+            @Override
+            public boolean condition() {
+                return false;
+            }
+        };
+        Assert.assertFalse(poller.callAndWait());
+    }
+
+    @Test
+    public void testCallUntilSuccess() throws InterruptedException {
+        AbstractPoller poller = new AbstractPoller(100, 5) {
+            int callNumber = 0;
+
+            @Override
+            public boolean call() {
+                callNumber += 1;
+                LOG.debug("Call nr " + callNumber);
+                return true;
+            }
+
+            @Override
+            public boolean condition() {
+                if (callNumber == 4) {
+                    return true;
+                }
+                return false;
+            }
+        };
+        Assert.assertTrue(poller.callUntilCondition());
+    }
+
+    @Test
+    public void testCallUntilFailure() throws InterruptedException {
+        AbstractPoller poller = new AbstractPoller(100, 5) {
+            @Override
+            public boolean call() {
+                return true;
+            }
+
+            @Override
+            public boolean condition() {
+                return false;
+            }
+        };
+        Assert.assertFalse(poller.callUntilCondition());
+    }
+
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 32/37: [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.4

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit d0c4868db75892da016f089330bfe3bfc0745253
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 15 16:19:23 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.4
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798854 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 836e0c3..aea08a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.3-SNAPSHOT</version>
+    <version>1.1.4</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.4</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.4</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.4</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 08/37: @trivial Added http status code to ClientException

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 5f0e667f148740b6c72b975aff09fc3a58db3782
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Fri Jun 24 13:55:02 2016 +0000

    @trivial Added http status code to ClientException
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1750101 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/testing/clients/ClientException.java | 4 ++++
 src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java  | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/ClientException.java b/src/main/java/org/apache/sling/testing/clients/ClientException.java
index a598564..379ece6 100644
--- a/src/main/java/org/apache/sling/testing/clients/ClientException.java
+++ b/src/main/java/org/apache/sling/testing/clients/ClientException.java
@@ -32,6 +32,10 @@ public class ClientException extends Exception {
         this(message, -1, throwable);
     }
 
+    public ClientException(String message, int htmlStatusCode) {
+        this(message, htmlStatusCode, null);
+    }
+
     public ClientException(String message, int htmlStatusCode, Throwable throwable) {
         super(message, throwable);
         this.httpStatusCode = htmlStatusCode;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java b/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
index c845b28..ba17460 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
@@ -99,7 +99,7 @@ public class HttpUtils {
         }
 
         // throw the exception
-        throw new ClientException(errorMsg);
+        throw new ClientException(errorMsg, givenStatus);
     }
 
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 31/37: SLING-6964 - SlingEmailClient does not allow accessing email headers

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit e249221a79565a79e2b5a33ea4f432c67d491f8b
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 15 16:12:33 2017 +0000

    SLING-6964 - SlingEmailClient does not allow accessing email headers
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798852 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/testing/clients/email/EmailMessage.java  | 44 ++++++++++++++++++++++
 .../testing/clients/email/SlingEmailClient.java    | 10 +++++
 .../sling/testing/clients/email/package-info.java  |  2 +-
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java b/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java
index e013e51..9ca1164 100644
--- a/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java
+++ b/src/main/java/org/apache/sling/testing/clients/email/EmailMessage.java
@@ -18,18 +18,62 @@
  */
 package org.apache.sling.testing.clients.email;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * Holds information retrieved from the mock SMTP server deployed in Sling
  *
  */
 public final class EmailMessage {
 	
+	public static final String HEADER_FROM = "From";
+	public static final String HEADER_TO = "To";
+	public static final String HEADER_SUBJECT = "Subject";
+	
+	private Map<String, String> headers = new LinkedHashMap<>();
+	
 	private String content;
 
 	public EmailMessage(String content) {
 		this.content = content;
 	}
 	
+	/**
+	 * Adds a new header to this email message
+	 * 
+	 * @param key the header name
+	 * @param value the header value
+	 */
+	public void addHeader(String key, String value) {
+		headers.put(key, value);
+	}
+	
+	/**
+	 * Returns the value of one of the headers of this email
+	 * 
+	 * @param key the header name
+	 * @return the value of the header, possibly <code>null</code>
+	 */
+	public String getHeader(String key) {
+		return headers.get(key);
+	}
+	
+	/**
+	 * Returns an unmodifiable view over the email headers
+	 * 
+	 * @return the headers
+	 */
+	public Map<String, String> getHeaders() {
+		return Collections.unmodifiableMap(headers);
+	}
+	
+	/**
+	 * Returns the contents of the email
+	 * 
+	 * @return the email content
+	 */
 	public String getContent() {
 		return content;
 	}
diff --git a/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java b/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java
index 65bdaa1..86c31b9 100644
--- a/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/email/SlingEmailClient.java
@@ -24,6 +24,7 @@ import static org.apache.http.HttpStatus.SC_OK;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.http.Header;
@@ -91,6 +92,15 @@ public final class SlingEmailClient extends SlingClient {
 			JsonNode messages = mapper.readTree(response.getContent());
 			for ( JsonNode emailNode : messages.get("messages") ) {
 				EmailMessage msg = new EmailMessage(emailNode.get(PN_CONTENT).getTextValue());
+				Iterator<String> fieldNames = emailNode.getFieldNames();
+				while ( fieldNames.hasNext() ) {
+					String fieldName = fieldNames.next();
+					if ( fieldName.equals(PN_CONTENT) ) {
+						continue;
+					}
+					msg.addHeader(fieldName, emailNode.get(fieldName).getTextValue());
+				}
+					
 				emails.add(msg);
 			}
 		} catch (IOException e) {
diff --git a/src/main/java/org/apache/sling/testing/clients/email/package-info.java b/src/main/java/org/apache/sling/testing/clients/email/package-info.java
index 373bccc..1789e2c 100644
--- a/src/main/java/org/apache/sling/testing/clients/email/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/email/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.testing.clients.email;
 
 import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 07/37: @trivial Added missing package version

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 88f90b06e94215b49ad7e13e4180ddba901ceff8
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Thu Jun 16 13:47:37 2016 +0000

    @trivial Added missing package version
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1748726 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/testing/clients/util/package-info.java   | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/src/main/java/org/apache/sling/testing/clients/util/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/package-info.java
new file mode 100644
index 0000000..048ebae
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util;
+
+import aQute.bnd.annotation.Version;
+

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 22/37: use OSGi annotations and remove unused Maven SCR Plugin

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 7915f4f0d9a11b25faa567b1a1a775b4a858c6e3
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Mon Mar 6 19:11:05 2017 +0000

    use OSGi annotations and remove unused Maven SCR Plugin
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1785736 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                               | 4 ----
 src/main/java/org/apache/sling/testing/clients/html/package-info.java | 3 +--
 .../java/org/apache/sling/testing/clients/instance/package-info.java  | 3 +--
 .../org/apache/sling/testing/clients/interceptors/package-info.java   | 3 +--
 src/main/java/org/apache/sling/testing/clients/osgi/package-info.java | 4 +++-
 src/main/java/org/apache/sling/testing/clients/package-info.java      | 3 +--
 .../apache/sling/testing/clients/util/config/impl/package-info.java   | 3 +--
 .../org/apache/sling/testing/clients/util/config/package-info.java    | 3 +--
 src/main/java/org/apache/sling/testing/clients/util/package-info.java | 3 +--
 .../org/apache/sling/testing/clients/util/poller/package-info.java    | 3 +--
 10 files changed, 11 insertions(+), 21 deletions(-)

diff --git a/pom.xml b/pom.xml
index af7f357..ff471a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,10 +43,6 @@
     
     <build>
         <plugins>
-           <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-scr-plugin</artifactId>
-            </plugin>
              <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
index 97b22cb..5f5a9f5 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -20,5 +20,4 @@
 @Version("2.1.0")
 package org.apache.sling.testing.clients.html;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/instance/package-info.java b/src/main/java/org/apache/sling/testing/clients/instance/package-info.java
index 43010e9..4d8063e 100644
--- a/src/main/java/org/apache/sling/testing/clients/instance/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/instance/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.1.0")
 package org.apache.sling.testing.clients.instance;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
index 49e7065..0f097b5 100644
--- a/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.0.1")
 package org.apache.sling.testing.clients.interceptors;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 3a2f85e..119cb90 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,5 +19,7 @@
 /**
  * OSGI testing tools.
  */
-@aQute.bnd.annotation.Version("1.1.0")
+@Version("1.1.0")
 package org.apache.sling.testing.clients.osgi;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
index d9cf961..c7b45c6 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.2.0")
 package org.apache.sling.testing.clients;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
index d6bfe66..e085e7e 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.0.0")
 package org.apache.sling.testing.clients.util.config.impl;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
index 59d0aaa..025052b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.0.0")
 package org.apache.sling.testing.clients.util.config;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/package-info.java
index 048ebae..7d4ac3b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.0.0")
 package org.apache.sling.testing.clients.util;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
index 6367401..4ff5bb0 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
@@ -20,5 +20,4 @@
 @Version("1.0.0")
 package org.apache.sling.testing.clients.util.poller;
 
-import aQute.bnd.annotation.Version;
-
+import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 29/37: [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.2

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 414ab8526ce20978e477fe70a5e4d8e9dd7168c6
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 15 13:03:39 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.2
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1798826 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 91ba15f..1ff3322 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.1-SNAPSHOT</version>
+    <version>1.1.2</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.2</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.2</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.2</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 27/37: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit f57f13c70944635e55a1ee63cfa4cf6cad995408
Author: Karl Pauls <pa...@apache.org>
AuthorDate: Fri Jun 2 21:22:03 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1797451 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 85aff6b..91ba15f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.0</version>
+    <version>1.1.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.0</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.0</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.0</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 35/37: SLING-7029 - Extension - adding method to stop a bundle

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit be0228bbfc1cb231812f6fc06495723084dfb18e
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Aug 7 08:09:35 2017 +0000

    SLING-7029 - Extension - adding method to stop a bundle
    
    Update exported version for org.apache.sling.testing.clients.osgi
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1804295 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/testing/clients/osgi/package-info.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 83ff1ab..2b92f60 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,7 +19,7 @@
 /**
  * OSGI testing tools.
  */
-@Version("1.2.0")
+@Version("1.3.0")
 package org.apache.sling.testing.clients.osgi;
 
 import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 03/37: trivial - reduced wait time in test

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 79179d9f8efd609aa7e4af92f0f3fc8fec5fe417
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Wed Jun 8 14:30:02 2016 +0000

    trivial - reduced wait time in test
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1747400 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/testing/DelayRequestInterceptorTest.java    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java b/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java
index c44d238..fc322b9 100644
--- a/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java
+++ b/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java
@@ -24,11 +24,11 @@ public class DelayRequestInterceptorTest {
 
     @Test
     public void testDelay() throws Exception {
-        DelayRequestInterceptor interceptor = new DelayRequestInterceptor(1000);
+        DelayRequestInterceptor interceptor = new DelayRequestInterceptor(700);
         long before = System.currentTimeMillis();
         interceptor.process(null, null);
         long after = System.currentTimeMillis();
-        Assert.assertTrue(after - before >= 1000);
+        Assert.assertTrue(after - before >= 700);
     }
 
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 09/37: Rollback release of org.apache.sling.hapi.client

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 7303bc31e6f733a61f424d90e893b7e0d81ed7f6
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Thu Jul 21 08:25:35 2016 +0000

    Rollback release of org.apache.sling.hapi.client
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1753649 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 026f356..3c80c86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,7 +35,6 @@
     <description>
         Sling testing http clients and utils
     </description>
-    
     <scm>
         <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
         <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 24/37: SLING-6853 Fixed incomplete patch.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 8de4abdfdd9c433a13634a963060eaf616b053b2
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Tue May 16 15:03:12 2017 +0000

    SLING-6853 Fixed incomplete patch.
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1795326 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/testing/clients/util/poller/Polling.java | 131 +++++++++++++++
 .../sling/testing/clients/HttpServerRule.java      |  84 ++++++++++
 .../testing/clients/SlingClientDoGetJsonTest.java  |  71 ++++++++
 .../testing/clients/SlingClientWaitExistsTest.java |  93 +++++++++++
 .../testing/clients/util/poller/PollingTest.java   | 179 +++++++++++++++++++++
 5 files changed, 558 insertions(+)

diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java b/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
new file mode 100644
index 0000000..faef6b2
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
@@ -0,0 +1,131 @@
+/*
+ * 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.testing.clients.util.poller;
+
+import org.apache.sling.testing.timeouts.TimeoutsProvider;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper for repeating a call until it returns true, with timeout capabilities.
+ * Subclasses should override the {@link #call()} method.
+ * Can be used with lambda expressions, using the constructor {@link #Polling(Callable c)}.
+ *
+ * @since 1.1.0
+ */
+public class Polling implements Callable<Boolean> {
+
+    /**
+     * Optional object to be used by the default implementation of call()
+     */
+    protected final Callable<Boolean> c;
+
+    /**
+     * Holder for the last exception thrown by call(), to be used for logging
+     */
+    protected Exception lastException;
+
+    /**
+     * Default constructor to be used in subclasses that override the {@link #call()} method.
+     * Should not be used directly on {@code Polling} instances, but only on extended classes.
+     * If used directly to get a {@code Polling} instance, executing {@link #poll(long timeout, long delay)}
+     * will be equivalent to {@code Thread.sleep(timeout)}
+     */
+    public Polling() {
+        this.c = null;
+        this.lastException = null;
+    }
+
+    /**
+     * Creates a new instance that uses the {@code Callable} parameter for polling
+     *
+     * @param c object whose {@code call()} method will be polled
+     */
+    public Polling(Callable<Boolean> c) {
+        this.c = c;
+        this.lastException = null;
+    }
+
+    /**
+     * <p>Method to be called by {@link #poll(long timeout, long delay)}, potentially multiple times,
+     * until it returns true or timeout is reached.<br/>
+     * Subclasses can override it to change the check accordingly. The method should return true
+     * only when the call was successful.<br/>
+     * It can return false or throw any {@code Exception} to make the poller try again later.</p>
+     *
+     * <p>The default implementation delegates the call to the {@code Callable c} instance.</p>
+     *
+     * @return {@code true} to end polling
+     * @throws Exception if unable to compute a result
+     */
+    @Override
+    public Boolean call() throws Exception {
+        if (c != null) {
+            return c.call();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * <p>Tries to execute {@link #call()} until it returns true or until {@code timeout} is reached.
+     * Between retries, it waits using {@code Thread.sleep(delay)}. It means the retry is not at a fixed pace,
+     * but depends on the execution time of the call itself.</p>
+     * <p>The method guarantees that the call() will be executed at least once. If the timeout is 0 or less, then
+     * call() will be executed exactly once.</p>
+     * <p>The timeout is adjusted using {@link TimeoutsProvider} so the final value can be changed using the
+     * system property: {@value org.apache.sling.testing.timeouts.TimeoutsProvider#PROP_TIMEOUT_MULTIPLIER}</p>
+     *
+     * @param timeout max total execution time, in milliseconds
+     * @param delay time to wait between calls, in milliseconds
+     *
+     * @throws TimeoutException if {@code timeout} was reached
+     * @throws InterruptedException if the thread was interrupted while sleeping; caller should throw it further
+     */
+    public void poll(long timeout, long delay) throws TimeoutException, InterruptedException {
+        long start = System.currentTimeMillis();
+        long effectiveTimeout = TimeoutsProvider.getInstance().getTimeout(timeout);
+
+        do {
+            try {
+                boolean success = call();
+                if (success) {
+                    return;
+                }
+                Thread.sleep(delay);
+            } catch (InterruptedException e) {
+                throw e;
+            } catch (Exception e) {
+                lastException = e;
+            }
+        } while (System.currentTimeMillis() < start + effectiveTimeout);
+
+        throw new TimeoutException(String.format(message(), effectiveTimeout, delay));
+    }
+
+    /**
+     * Returns the string to be used in the {@code TimeoutException}, if needed.
+     * The string is passed to {@code String.format(message(), timeout, delay)}, so it can be a format
+     * including {@code %1$} and {@code %2$}. The field {@code lastException} is also available for logging
+     *
+     * @return the format string
+     */
+    protected String message() {
+        return "Call failed to return true in %1$d ms. Last exception was: " + lastException;
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/clients/HttpServerRule.java b/src/test/java/org/apache/sling/testing/clients/HttpServerRule.java
new file mode 100644
index 0000000..926f79b
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/HttpServerRule.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.testing.clients;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.impl.bootstrap.HttpServer;
+import org.apache.http.impl.bootstrap.ServerBootstrap;
+import org.apache.http.localserver.SSLTestContexts;
+import org.junit.rules.ExternalResource;
+
+/** JUnit Rule that starts an HTTP server */
+public class HttpServerRule extends ExternalResource {
+    public static final String ORIGIN = "TEST/1.1";
+    private HttpServer server;
+    private HttpHost host;
+    private URI uri;
+
+    protected ServerBootstrap serverBootstrap;
+
+    public static enum ProtocolScheme {
+        http,
+        https;
+        private ProtocolScheme() {
+        }
+    }
+
+    protected final ProtocolScheme protocolScheme;
+
+    public HttpServerRule() {
+        this(ProtocolScheme.http);
+    }
+
+    public HttpServerRule(ProtocolScheme protocolScheme) {
+        this.protocolScheme = protocolScheme;
+    }
+
+    @Override
+    protected void after() {
+        server.shutdown(-1, TimeUnit.SECONDS);
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        final SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(5000).build();
+        serverBootstrap = ServerBootstrap.bootstrap().setSocketConfig(socketConfig).setServerInfo(ORIGIN);
+        if(ProtocolScheme.https.equals(protocolScheme)) {
+            serverBootstrap.setSslContext(SSLTestContexts.createServerSSLContext());
+        }
+        registerHandlers();
+        server = serverBootstrap.create();
+        server.start();
+        host = new HttpHost("127.0.0.1", server.getLocalPort(), protocolScheme.name());
+        uri = URIUtils.rewriteURI(new URI("/"), host);
+    }
+
+    protected void registerHandlers() throws IOException {
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/testing/clients/SlingClientDoGetJsonTest.java b/src/test/java/org/apache/sling/testing/clients/SlingClientDoGetJsonTest.java
new file mode 100644
index 0000000..6eeba23
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/SlingClientDoGetJsonTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.testing.clients;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.codehaus.jackson.JsonNode;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+public class SlingClientDoGetJsonTest {
+    private static final String GET_JSON_PATH = "/test/json/resource";
+    private static final String JSON_RESPONSE = "{\"jcr:primaryType\":\"cq:Page\",\"jcr:createdBy\":\"admin-json\"}";
+    private static final String JSON_INF_RESPONSE = "{\"jcr:primaryType\":\"cq:Page\",\"jcr:createdBy\":\"admin-infinity\"}";
+
+    @ClassRule
+    public static HttpServerRule httpServer = new HttpServerRule() {
+        @Override
+        protected void registerHandlers() throws IOException {
+            serverBootstrap.registerHandler(GET_JSON_PATH + ".1.json", new HttpRequestHandler() {
+                @Override
+                public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
+                    response.setEntity(new StringEntity(JSON_RESPONSE));
+                }
+            });
+
+            serverBootstrap.registerHandler(GET_JSON_PATH + ".infinity.json", new HttpRequestHandler() {
+                @Override
+                public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
+                    response.setEntity(new StringEntity(JSON_INF_RESPONSE));
+                }
+            });
+        }
+    };
+
+    @Test
+    public void testDoGetJson() throws Exception {
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        JsonNode res = c.doGetJson(GET_JSON_PATH, 1, 200);
+        assertEquals("admin-json", res.get("jcr:createdBy").getTextValue());
+    }
+
+    @Test
+    public void testDoGetJsonInfinity() throws Exception {
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        JsonNode res = c.doGetJson(GET_JSON_PATH, -1, 200);
+        assertEquals("admin-infinity", res.get("jcr:createdBy").getTextValue());
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/clients/SlingClientWaitExistsTest.java b/src/test/java/org/apache/sling/testing/clients/SlingClientWaitExistsTest.java
new file mode 100644
index 0000000..31b67bf
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/SlingClientWaitExistsTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.testing.clients;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.*;
+
+public class SlingClientWaitExistsTest {
+    private static final String GET_WAIT_PATH = "/test/wait/resource";
+    private static final String OK_RESPONSE = "TEST_OK";
+    private static final String NOK_RESPONSE = "TEST_OK";
+
+    private static int waitCount = 4; // truly randomly chosen by typing with the eyes closed
+    private static int callCount = 0;
+
+    @ClassRule
+    public static HttpServerRule httpServer = new HttpServerRule() {
+        @Override
+        protected void registerHandlers() throws IOException {
+            serverBootstrap.registerHandler(GET_WAIT_PATH + ".json", new HttpRequestHandler() {
+                @Override
+                public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
+                    callCount++;
+                    if (callCount == waitCount) {
+                        response.setEntity(new StringEntity(OK_RESPONSE));
+                    } else {
+                        response.setEntity(new StringEntity(NOK_RESPONSE));
+                        response.setStatusCode(404);
+                    }
+                }
+            });
+        }
+    };
+
+    @Test
+    public void testWaitExists() throws Exception {
+        callCount = 0;  // reset counter
+        waitCount = 3;  // less than timeout
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        c.waitExists(GET_WAIT_PATH, 500, 10);
+        assertEquals(waitCount, callCount);
+    }
+
+    @Test
+    public void testWaitExistsTimeout() throws Exception {
+        callCount = 0;  // reset counter
+        waitCount = 40;  // to be sure we reach timeout
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        try {
+            c.waitExists(GET_WAIT_PATH, 200, 10);
+        } catch (TimeoutException e ) {
+            assertTrue("call was executed only " + callCount + " times", callCount > 3);
+            return;
+        }
+
+        fail("waitExists did not timeout");
+    }
+
+    @Test
+    public void testWaitExistsOnce() throws Exception {
+        callCount = 0;  // reset counter
+        waitCount = 1;  // less than timeout
+        SlingClient c = new SlingClient(httpServer.getURI(), "user", "pass");
+        c.waitExists(GET_WAIT_PATH, -1, 10);
+        assertEquals(1, callCount);
+    }
+}
diff --git a/src/test/java/org/apache/sling/testing/clients/util/poller/PollingTest.java b/src/test/java/org/apache/sling/testing/clients/util/poller/PollingTest.java
new file mode 100644
index 0000000..15994ed
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/clients/util/poller/PollingTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.testing.clients.util.poller;
+
+import org.apache.commons.lang3.mutable.MutableBoolean;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class PollingTest {
+    @Test
+    public void testCallOnce() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                return true;
+            }
+        };
+        p.poll(500, 10);
+
+        assertEquals(1, callCount.intValue());
+    }
+
+    @Test
+    public void testCallTwice() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        final MutableBoolean called = new MutableBoolean(false);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                boolean b = called.booleanValue();
+                called.setTrue();
+                return b;
+            }
+        };
+        p.poll(500, 10);
+
+        assertEquals(2, callCount.intValue());
+    }
+
+    @Test
+    public void testCallTimeout() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                return false;
+            }
+        };
+
+        try {
+            p.poll(100, 10);
+        } catch (TimeoutException e ) {
+            assertTrue("Expected to execute call() at least 4 times, got instead only " + callCount.intValue() + " calls",
+                    callCount.intValue() > 5);
+            return;
+        }
+
+        fail("Did not reach timeout");
+    }
+
+    @Test
+    public void testNegativeTimeout() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                return true;
+            }
+        };
+        p.poll(-1, 10);
+
+        assertEquals(1, callCount.intValue());
+    }
+
+    //
+    // Tests with Callable
+    //
+
+    @Test
+    public void testCallableOnce() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        final MutableBoolean called = new MutableBoolean(false);
+        Polling p = new Polling(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                return true;
+            }
+        });
+        p.poll(500, 10);
+
+        assertEquals(1, callCount.intValue());
+    }
+
+    @Test
+    public void testCallableTwice() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        final MutableBoolean called = new MutableBoolean(false);
+        Polling p = new Polling(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                boolean b = called.booleanValue();
+                called.setTrue();
+                return b;
+            }
+        });
+        p.poll(500, 10);
+
+        assertEquals(2, callCount.intValue());
+    }
+
+    @Test
+    public void testCallableTimeout() throws Exception {
+        final MutableInt callCount = new MutableInt(0);
+        Polling p = new Polling(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                callCount.increment();
+                return false;
+            }
+        });
+
+        try {
+            p.poll(100, 10);
+        } catch (TimeoutException e ) {
+            assertTrue("Expected to execute call() at least 4 times, got instead only " + callCount.intValue() + " calls",
+                    callCount.intValue() > 5);
+            return;
+        }
+
+        fail("Did not reach timeout");
+    }
+
+
+    @Test
+    public void testCallPriority() throws Exception {
+        Polling p = new Polling(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                return false;
+            }
+        }) {
+            @Override
+            public Boolean call() throws Exception {
+                return true;
+            }
+        };
+
+        // Should not reach timeout since overridden call() has priority over Callable param
+        p.poll(100, 10);
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 26/37: [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit e58b2e98c5ec0fc5189e8bb1ee9400029d8a7497
Author: Karl Pauls <pa...@apache.org>
AuthorDate: Fri Jun 2 21:21:45 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.clients-1.1.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1797449 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1b42b85..85aff6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.1.0-SNAPSHOT</version>
+    <version>1.1.0</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -36,9 +36,9 @@
         Sling testing http clients and utils
     </description>
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</connection>
-        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/http/clients</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.0</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.clients-1.1.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.clients-1.1.0</url>
     </scm>
     
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 37/37: SLING-7167 Adjust READMEs

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 102ac8a2265eb0e6069a3a4b27ddc91057e1ec04
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Tue Oct 3 10:10:41 2017 +0000

    SLING-7167 Adjust READMEs
    
    add uniform header linking to Sling project
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1810947 13f79535-47bb-0310-9956-ffa450edef68
---
 README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index adce441..ee92a84 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# Sling Http Clients
+# Apache Sling Testing Clients
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
 
 `SlingClient` is a specialized
 [`HttpClient`](https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/HttpClient.html)

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-clients] 17/37: SLING-6405 - Make testing.clients.interceptors.TestDescriptionInterceptor in line with TestLogServlet from junit core

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 1ded2db52471e7ccbd00c0e3011f95cf2bfc54e3
Author: Chetan Mehrotra <ch...@apache.org>
AuthorDate: Thu Jan 5 08:28:33 2017 +0000

    SLING-6405 - Make testing.clients.interceptors.TestDescriptionInterceptor in line with TestLogServlet from junit core
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1777434 13f79535-47bb-0310-9956-ffa450edef68
---
 .../clients/interceptors/TestDescriptionInterceptor.java  | 15 +++++++++++----
 .../sling/testing/clients/interceptors/package-info.java  |  2 +-
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
index 38b269e..ad91912 100644
--- a/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
@@ -32,11 +32,18 @@ import java.io.IOException;
  * @see org.slf4j.MDC http://www.slf4j.org/manual.html
  */
 public class TestDescriptionInterceptor implements HttpRequestInterceptor{
-    public static final String TEST_NAME_HEADER = "sling.test.name";
-    public static final String TEST_CLASS_HEADER = "sling.test.class";
+    //Same headers are defined in TestLogServlet
+    public static final String TEST_CLASS_HEADER = "X-Sling-TestClass";
+    public static final String TEST_NAME_HEADER = "X-Sling-TestName";
 
     public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
-            httpRequest.addHeader(TEST_NAME_HEADER, TestDescriptionHolder.getMethodName());
-            httpRequest.addHeader(TEST_CLASS_HEADER, TestDescriptionHolder.getClassName());
+        addHeader(httpRequest, TEST_NAME_HEADER, TestDescriptionHolder.getMethodName());
+        addHeader(httpRequest, TEST_CLASS_HEADER, TestDescriptionHolder.getClassName());
+    }
+
+    private static void addHeader(HttpRequest httpRequest, String name, String value){
+        if (value != null){
+            httpRequest.addHeader(name, value);
+        }
     }
 }
diff --git a/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
index 1de07e5..49e7065 100644
--- a/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("1.0.1")
 package org.apache.sling.testing.clients.interceptors;
 
 import aQute.bnd.annotation.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.