You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2016/04/29 16:03:33 UTC

svn commit: r1741632 [1/4] - in /sling/trunk/testing/http: ./ clients/ clients/src/ clients/src/main/ clients/src/main/java/ clients/src/main/java/org/ clients/src/main/java/org/apache/ clients/src/main/java/org/apache/sling/ clients/src/main/java/org/...

Author: bdelacretaz
Date: Fri Apr 29 14:03:32 2016
New Revision: 1741632

URL: http://svn.apache.org/viewvc?rev=1741632&view=rev
Log:
SLING-5703 - new http/clients module, extracted and enhanced from testing/tools. Contributed by Andrei Dulvac, thanks!

Added:
    sling/trunk/testing/http/
    sling/trunk/testing/http/clients/   (with props)
    sling/trunk/testing/http/clients/README.md
    sling/trunk/testing/http/clients/pom.xml
    sling/trunk/testing/http/clients/src/
    sling/trunk/testing/http/clients/src/main/
    sling/trunk/testing/http/clients/src/main/java/
    sling/trunk/testing/http/clients/src/main/java/org/
    sling/trunk/testing/http/clients/src/main/java/org/apache/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/ClientException.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/Constants.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/SlingClient.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/SlingClientConfig.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/SlingHttpResponse.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/instance/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/instance/InstanceConfiguration.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/instance/InstanceSetup.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/DelayRequestInterceptor.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieHolder.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieInterceptor.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/StickyCookieSpec.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionHolder.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/TestDescriptionInterceptor.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/interceptors/package-info.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Bundle.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundleInfo.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInfo.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Component.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/package-info.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/impl/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/poller/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/timeouts/
    sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java
    sling/trunk/testing/http/clients/src/test/
    sling/trunk/testing/http/clients/src/test/java/
    sling/trunk/testing/http/clients/src/test/java/org/
    sling/trunk/testing/http/clients/src/test/java/org/apache/
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/DelayRequestInterceptorTest.java
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/util/
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/util/UniquePathsTest.java
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/util/poller/
    sling/trunk/testing/http/clients/src/test/java/org/apache/sling/testing/util/poller/AbstractPollerTest.java

Propchange: sling/trunk/testing/http/clients/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Apr 29 14:03:32 2016
@@ -0,0 +1,19 @@
+target
+sling
+bin
+logs
+jackrabbit-repository
+derby.log
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+jackrabbit
+
+
+
+

Added: sling/trunk/testing/http/clients/README.md
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/README.md?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/README.md (added)
+++ sling/trunk/testing/http/clients/README.md Fri Apr 29 14:03:32 2016
@@ -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.
+

Added: sling/trunk/testing/http/clients/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/pom.xml?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/pom.xml (added)
+++ sling/trunk/testing/http/clients/pom.xml Fri Apr 29 14:03:32 2016
@@ -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>

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/AbstractSlingClient.java Fri Apr 29 14:03:32 2016
@@ -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);
+    }
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/ClientException.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/ClientException.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/ClientException.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/ClientException.java Fri Apr 29 14:03:32 2016
@@ -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;
+    }
+
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/Constants.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/Constants.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/Constants.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/Constants.java Fri Apr 29 14:03:32 2016
@@ -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";
+}