From 4407c7ce9ec70a1e912e6c43bbebe861faf5cd65 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 30 Apr 2015 07:17:52 +0200 Subject: [PATCH 1/7] start implementation of a new advanced http client, in order to fix issue #709 --- .../sonia/scm/net/ahc/AdvancedHttpClient.java | 189 ++++++++ .../scm/net/ahc/AdvancedHttpRequest.java | 72 ++++ .../net/ahc/AdvancedHttpRequestWithBody.java | 216 ++++++++++ .../scm/net/ahc/AdvancedHttpResponse.java | 199 +++++++++ .../sonia/scm/net/ahc/BaseHttpRequest.java | 408 ++++++++++++++++++ .../sonia/scm/net/ahc/ByteSourceContent.java | 97 +++++ .../main/java/sonia/scm/net/ahc/Content.java | 69 +++ .../java/sonia/scm/net/ahc/FileContent.java | 109 +++++ .../sonia/scm/net/ahc/FormContentBuilder.java | 139 ++++++ .../java/sonia/scm/net/ahc/HttpMethod.java | 64 +++ .../java/sonia/scm/net/ahc/RawContent.java | 109 +++++ .../java/sonia/scm/net/ahc/StringContent.java | 58 +++ .../java/sonia/scm/net/ahc/package-info.java | 36 ++ .../scm/net/ahc/AdvancedHttpClientTest.java | 104 +++++ .../scm/net/ahc/AdvancedHttpRequestTest.java | 57 +++ .../ahc/AdvancedHttpRequestWithBodyTest.java | 127 ++++++ .../scm/net/ahc/AdvancedHttpResponseTest.java | 91 ++++ .../scm/net/ahc/BaseHttpRequestTest.java | 142 ++++++ .../scm/net/ahc/ByteSourceContentTest.java | 74 ++++ .../sonia/scm/net/ahc/FileContentTest.java | 116 +++++ .../scm/net/ahc/FormContentBuilderTest.java | 95 ++++ .../sonia/scm/net/ahc/RawContentTest.java | 72 ++++ .../sonia/scm/net/ahc/StringContentTest.java | 59 +++ .../net/ahc/DefaultAdvancedHttpClient.java | 319 ++++++++++++++ .../net/ahc/DefaultAdvancedHttpResponse.java | 194 +++++++++ 25 files changed, 3215 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/Content.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/package-info.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java create mode 100644 scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java create mode 100644 scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java create mode 100644 scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java new file mode 100644 index 0000000000..943d1a0352 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +/** + * Advanced client for http operations. The {@link AdvancedHttpClient} replaces + * the much more simpler implementation {@link sonia.scm.net.HttpClient}. The + * {@link AdvancedHttpClient} offers a fluid interface for handling most common + * http operations. The {@link AdvancedHttpClient} can be injected by the + * default injection mechanism of SCM-Manager. + *

 

+ * Http GET example: + * + *

+ * AdvancedHttpResponse response = client.get("https://www.scm-manager.org")
+ *                                       .decodeGZip(true)
+ *                                       .request();
+ * 
+ * System.out.println(response.contentAsString());
+ * 
+ * + *

 

+ * Http POST example: + * + *

+ * AdvancedHttpResponse response = client.post("https://www.scm-manager.org")
+ *                                       .formContent()
+ *                                       .field("firstname", "Tricia")
+ *                                       .field("lastname", "McMillan")
+ *                                       .build()
+ *                                       .request();
+ * 
+ * if (response.isSuccessful()){
+ *   System.out.println("success");
+ * }
+ * 
+ * + * @author Sebastian Sdorra + * @since 1.46 + * + * @apiviz.landmark + */ +public abstract class AdvancedHttpClient +{ + + /** + * Executes the given request and returns the http response. Implementation + * have to check, if the instance if from type + * {@link AdvancedHttpRequestWithBody} in order to handle request contents. + * + * + * @param request request to execute + * + * @return http response + * + * @throws IOException + */ + protected abstract AdvancedHttpResponse request(BaseHttpRequest request) + throws IOException; + + /** + * Returns a builder for a DELETE request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequestWithBody delete(String url) + { + return new AdvancedHttpRequestWithBody(this, HttpMethod.DELETE, url); + } + + /** + * Returns a builder for a HEAD request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequest head(String url) + { + return new AdvancedHttpRequest(this, HttpMethod.HEAD, url); + } + + /** + * Returns a request builder with a custom method. Note: not + * every method is supported by the underlying implementation of the http + * client. + * + * + * @param method http method + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequestWithBody method(String method, String url) + { + return new AdvancedHttpRequestWithBody(this, method, url); + } + + /** + * Returns a builder for a OPTIONS request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequestWithBody options(String url) + { + return new AdvancedHttpRequestWithBody(this, HttpMethod.OPTIONS, url); + } + + /** + * Returns a builder for a POST request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequestWithBody post(String url) + { + return new AdvancedHttpRequestWithBody(this, HttpMethod.POST, url); + } + + /** + * Returns a builder for a PUT request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequestWithBody put(String url) + { + return new AdvancedHttpRequestWithBody(this, HttpMethod.PUT, url); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns a builder for a GET request. + * + * + * @param url request url + * + * @return request builder + */ + public AdvancedHttpRequest get(String url) + { + return new AdvancedHttpRequest(this, HttpMethod.GET, url); + } +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java new file mode 100644 index 0000000000..b8940628b4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- JDK imports ------------------------------------------------------------ + +/** + * An http request without {@link Content} for example a GET or HEAD request. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class AdvancedHttpRequest extends BaseHttpRequest +{ + + /** + * Constructs a new {@link AdvancedHttpRequest} + * + * + * @param client + * @param method + * @param url + */ + AdvancedHttpRequest(AdvancedHttpClient client, String method, + String url) + { + super(client, method, url); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Returns {@code this}. + * + * + * @return {@code this} + */ + @Override + protected AdvancedHttpRequest self() + { + return this; + } +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java new file mode 100644 index 0000000000..1acad0efb0 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +import java.nio.charset.Charset; + +/** + * Http request with body. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class AdvancedHttpRequestWithBody + extends BaseHttpRequest +{ + + /** + * Constructs a new {@link AdvancedHttpRequestWithBody}. + * + * + * @param client http client + * @param method http method + * @param url url + */ + AdvancedHttpRequestWithBody(AdvancedHttpClient client, String method, + String url) + { + super(client, method, url); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Sets the content length for the request. + * + * + * @param length content length + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody contentLength(long length) + { + return header("Content-Length", String.valueOf(length)); + } + + /** + * Sets the content type for the request. + * + * + * @param contentType content type + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody contentType(String contentType) + { + return header("Content-Type", contentType); + } + + /** + * Sets the content of the file as request content. + * + * + * @param file file + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody fileContent(File file) + { + this.content = new FileContent(file); + + return this; + } + + /** + * Returns a {@link FormContentBuilder}. The builder can be used to add form + * parameters as content for the request. Note: you have to + * call {@link FormContentBuilder#build()} in order to apply the form content + * to the request. + * + * @return form content builder + */ + public FormContentBuilder formContent() + { + return new FormContentBuilder(this); + } + + /** + * Sets the raw data as request content. + * + * + * @param data raw data + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody rawContent(byte[] data) + { + this.content = new RawContent(data); + + return this; + } + + /** + * Sets the raw data as request content. + * + * + * @param source byte source + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody rawContent(ByteSource source) + { + this.content = new ByteSourceContent(source); + + return this; + } + + /** + * Sets the string as request content. + * + * + * @param content string content + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody stringContent(String content) + { + return stringContent(content, Charsets.UTF_8); + } + + /** + * Sets the string as request content. + * + * + * @param content string content + * @param charset charset of content + * + * @return {@code this} + */ + public AdvancedHttpRequestWithBody stringContent(String content, + Charset charset) + { + this.content = new StringContent(content, charset); + + return this; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns the content or the request. + * + * + * @return request content + */ + public Content getContent() + { + return content; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Returns {@code this}. + * + * + * @return {@code this} + */ + @Override + protected AdvancedHttpRequestWithBody self() + { + return this; + } + + //~--- fields --------------------------------------------------------------- + + /** request content */ + private Content content; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java new file mode 100644 index 0000000000..6148b3b355 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteSource; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; + +/** + * Http response. The response of a {@link AdvancedHttpRequest} or + * {@link AdvancedHttpRequestWithBody}. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public abstract class AdvancedHttpResponse +{ + + //~--- methods -------------------------------------------------------------- + + /** + * Returns the content of the response as byte array. + * + * + * @return content as byte array + * + * @throws IOException + */ + public byte[] content() throws IOException + { + ByteSource content = contentAsByteSource(); + byte[] data = null; + + if (content != null) + { + data = content.read(); + } + + return data; + } + + /** + * Returns a reader for the content of the response. + * + * + * @return read of response content + * + * @throws IOException + */ + public BufferedReader contentAsReader() throws IOException + { + ByteSource content = contentAsByteSource(); + BufferedReader reader = null; + if (content != null) + { + reader = content.asCharSource(Charsets.UTF_8).openBufferedStream(); + } + + return reader; + } + + /** + * Returns response content as stream. + * + * + * @return response content as stram + * + * @throws IOException + */ + public InputStream contentAsStream() throws IOException + { + ByteSource content = contentAsByteSource(); + InputStream stream = null; + if (content != null) + { + stream = content.openBufferedStream(); + } + + return stream; + } + + /** + * Returns the response content as byte source. + * + * + * @return response content as byte source + * @throws IOException + */ + public abstract ByteSource contentAsByteSource() throws IOException; + + /** + * Returns the response content as string. + * + * + * @return response content + * + * @throws IOException + */ + public String contentAsString() throws IOException + { + ByteSource content = contentAsByteSource(); + String value = null; + if (content != null) + { + value = content.asCharSource(Charsets.UTF_8).read(); + } + + return value; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns the first header value for the given header name or {@code null}. + * + * + * @param name header name + * + * @return header value or {@code null} + */ + public String getFirstHeader(String name) + { + return Iterables.getFirst(getHeaders().get(name), null); + } + + /** + * Returns the response headers. + * + * + * @return response headers + */ + public abstract Multimap getHeaders(); + + /** + * Returns {@code true} if the response was successful. A response is + * successful, if the status code is greater than 199 and lower than 400. + * + * @return {@code true} if the response was successful + */ + public boolean isSuccessful() + { + int status = getStatus(); + return status > 199 && status < 400; + } + + /** + * Returns the status code of the response. + * + * + * @return status code + */ + public abstract int getStatus(); + + /** + * Returns the status text of the response. + * + * + * @return status text + */ + public abstract String getStatusText(); +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java new file mode 100644 index 0000000000..f48e819ddc --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java @@ -0,0 +1,408 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import org.apache.shiro.codec.Base64; + +import sonia.scm.util.HttpUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +/** + * Base class for http requests. + * + * @author Sebastian Sdorra + * @param request implementation + * + * @since 1.46 + */ +public abstract class BaseHttpRequest +{ + + /** + * Constructs a new {@link BaseHttpRequest}. + * + * + * @param client http client + * @param method http method + * @param url url + */ + public BaseHttpRequest(AdvancedHttpClient client, String method, String url) + { + this.client = client; + this.method = method; + this.url = url; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Executes the request and returns the http response. + * + * + * @return http response + * + * @throws IOException + */ + public AdvancedHttpResponse request() throws IOException + { + return client.request(this); + } + + /** + * Implementing classes should return {@code this}. + * + * + * @return request instance + */ + protected abstract T self(); + + /** + * Enabled http basic authentication. + * + * + * @param username username for http basic authentication + * @param password password for http basic authentication + * + * @return http request instance + */ + public T basicAuth(String username, String password) + { + String auth = Strings.nullToEmpty(username).concat(":").concat( + Strings.nullToEmpty(password)); + + auth = Base64.encodeToString(auth.getBytes(Charsets.ISO_8859_1)); + headers.put("Authorization", "Basic ".concat(auth)); + + return self(); + } + + /** + * Enable or disabled gzip decoding. The default value is false. + * + * + * @param decodeGZip true to enable gzip decoding + * + * @return request instance + */ + public T decodeGZip(boolean decodeGZip) + { + this.decodeGZip = decodeGZip; + + return self(); + } + + /** + * Enable or disable certificate validation of ssl certificates. The default + * value is false. + * + * + * @param disableCertificateValidation true to disable certificate validation + * + * @return request instance + */ + public T disableCertificateValidation(boolean disableCertificateValidation) + { + this.disableCertificateValidation = disableCertificateValidation; + + return self(); + } + + /** + * Enable or disable the validation of ssl hostnames. The default value is + * false. + * + * + * @param disableHostnameValidation true to disable ssl hostname validation + * + * @return request instance + */ + public T disableHostnameValidation(boolean disableHostnameValidation) + { + this.disableHostnameValidation = disableHostnameValidation; + + return self(); + } + + /** + * Add http headers to request. + * + * + * @param name header name + * @param values header values + * + * @return request instance + */ + public T header(String name, Object... values) + { + for (Object v : values) + { + headers.put(name, toString(v)); + } + + return self(); + } + + /** + * Add http headers to request. + * + * + * @param name header name + * @param values header values + * + * @return request instance + */ + public T headers(String name, Iterable values) + { + for (Object v : values) + { + headers.put(name, toString(v)); + } + + return self(); + } + + /** + * Ignore proxy settings. The default value is false. + * + * + * @param ignoreProxySettings true to ignore proxy settings. + * + * @return request instance + */ + public T ignoreProxySettings(boolean ignoreProxySettings) + { + this.ignoreProxySettings = ignoreProxySettings; + + return self(); + } + + /** + * Appends a query parameter to the request. + * + * + * @param name name of query parameter + * @param values query parameter values + * + * @return request instance + */ + public T queryString(String name, Object... values) + { + for (Object v : values) + { + appendQueryString(name, v); + } + + return self(); + } + + /** + * Appends a query parameter to the request. + * + * + * @param name name of query parameter + * @param values query parameter values + * + * @return request instance + */ + public T queryStrings(String name, Iterable values) + { + for (Object v : values) + { + appendQueryString(name, v); + } + + return self(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Return a map with http headers used for the request. + * + * + * @return map with http headers + */ + public Multimap getHeaders() + { + return headers; + } + + /** + * Returns the http method for the request. + * + * + * @return http method of request + */ + public String getMethod() + { + return method; + } + + /** + * Returns the url for the request. + * + * + * @return url of the request + */ + public String getUrl() + { + return url; + } + + /** + * Returns true if the request decodes gzip compression. + * + * + * @return true if the request decodes gzip compression + */ + public boolean isDecodeGZip() + { + return decodeGZip; + } + + /** + * Returns true if the verification of ssl certificates is disabled. + * + * + * @return true if certificate verification is disabled + */ + public boolean isDisableCertificateValidation() + { + return disableCertificateValidation; + } + + /** + * Returns true if the ssl hostname validation is disabled. + * + * + * @return true if the ssl hostname validation is disabled + */ + public boolean isDisableHostnameValidation() + { + return disableHostnameValidation; + } + + /** + * Returns true if the proxy settings are ignored. + * + * + * @return true if the proxy settings are ignored + */ + public boolean isIgnoreProxySettings() + { + return ignoreProxySettings; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Returns the value url encoded. + * + * + * @param value value to encode + * + * @return encoded value + */ + protected String encoded(Object value) + { + return HttpUtil.encode(Strings.nullToEmpty(toString(value))); + } + + /** + * Returns string representation of the given object or {@code null}, if the + * object is {@code null}. + * + * + * @param object object + * + * @return string representation or {@code null} + */ + protected String toString(Object object) + { + return (object != null) + ? object.toString() + : null; + } + + private void appendQueryString(String name, Object value) + { + StringBuilder buffer = new StringBuilder(); + + if (url.contains("?")) + { + buffer.append("&"); + } + else + { + buffer.append("?"); + } + + buffer.append(encoded(name)).append("=").append(encoded(value)); + url = url.concat(buffer.toString()); + } + + //~--- fields --------------------------------------------------------------- + + /** http client */ + protected final AdvancedHttpClient client; + + /** http header */ + private final Multimap headers = HashMultimap.create(); + + /** http method */ + private final String method; + + /** ignore proxy settings */ + private boolean ignoreProxySettings = false; + + /** disable ssl hostname validation */ + private boolean disableHostnameValidation = false; + + /** disable ssl certificate validation */ + private boolean disableCertificateValidation = false; + + /** decode gzip */ + private boolean decodeGZip = false; + + /** url of request */ + private String url; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java new file mode 100644 index 0000000000..f4182c0975 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/ByteSourceContent.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.io.ByteSource; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.OutputStream; + +/** + * {@link ByteSource} content for {@link AdvancedHttpRequestWithBody}. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class ByteSourceContent implements Content +{ + + /** + * Constructs a new {@link ByteSourceContent}. + * + * + * @param byteSource byte source + */ + public ByteSourceContent(ByteSource byteSource) + { + this.byteSource = byteSource; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Sets the content length for the request. + * + * + * @param request http request + * + * @throws IOException + */ + @Override + public void prepare(AdvancedHttpRequestWithBody request) throws IOException + { + request.contentLength(byteSource.size()); + } + + /** + * Copies the content of the byte source to the output stream. + * + * + * @param output output stream + * + * @throws IOException + */ + @Override + public void process(OutputStream output) throws IOException + { + byteSource.copyTo(output); + } + + //~--- fields --------------------------------------------------------------- + + /** byte source */ + private final ByteSource byteSource; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/Content.java b/scm-core/src/main/java/sonia/scm/net/ahc/Content.java new file mode 100644 index 0000000000..0f73e7b0d6 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/Content.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Content of a {@link AdvancedHttpRequestWithBody}. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public interface Content +{ + + /** + * Prepares the {@link AdvancedHttpRequestWithBody} for the request content. + * Implementations can set the content type, content length or custom headers + * for the request. + * + * + * @param request request + * + * @throws IOException + */ + public void prepare(AdvancedHttpRequestWithBody request) throws IOException; + + /** + * Copies the content to the output stream. + * + * + * @param output output stream + * + * @throws IOException + */ + public void process(OutputStream output) throws IOException; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java new file mode 100644 index 0000000000..51f1af6c21 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/FileContent.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.io.ByteStreams; +import com.google.common.io.Closeables; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Sets the content of the file to the request. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class FileContent implements Content +{ + + /** + * Constructs a new {@link FileContent}. + * + * + * @param file file + */ + public FileContent(File file) + { + this.file = file; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Sets the content length of the file as request header. + * + * + * @param request request + */ + @Override + public void prepare(AdvancedHttpRequestWithBody request) + { + request.contentLength(file.length()); + } + + /** + * Copies the content of the file to the output stream. + * + * + * @param output output stream + * + * @throws IOException + */ + @Override + public void process(OutputStream output) throws IOException + { + InputStream stream = null; + + try + { + stream = new FileInputStream(file); + ByteStreams.copy(stream, output); + } + finally + { + Closeables.close(stream, true); + } + } + + //~--- fields --------------------------------------------------------------- + + /** file */ + private final File file; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java b/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java new file mode 100644 index 0000000000..d25c12ce94 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/FormContentBuilder.java @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; + +import sonia.scm.util.HttpUtil; + +/** + * The form builder is able to add form parameters to a request. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class FormContentBuilder +{ + + /** + * Constructs a new {@link FormContentBuilder}. + * + * + * @param request request + */ + public FormContentBuilder(AdvancedHttpRequestWithBody request) + { + this.request = request; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Build the formular content and append it to the request. + * + * + * @return request instance + */ + public AdvancedHttpRequestWithBody build() + { + request.contentType("application/x-www-form-urlencoded"); + request.stringContent(builder.toString()); + + return request; + } + + /** + * Adds a formular parameter. + * + * + * @param name parameter name + * @param values parameter values + * + * @return {@code this} + */ + public FormContentBuilder fields(String name, Iterable values) + { + for (Object v : values) + { + append(name, v); + } + + return this; + } + + /** + * Adds a formular parameter. + * + * + * @param name parameter name + * @param values parameter values + * + * @return {@code this} + */ + public FormContentBuilder field(String name, Object... values) + { + for (Object v : values) + { + append(name, v); + } + + return this; + } + + private void append(String name, Object value) + { + if (!Strings.isNullOrEmpty(name)) + { + if (builder.length() > 0) + { + builder.append("&"); + } + + builder.append(HttpUtil.encode(name)).append("="); + + if (value != null) + { + builder.append(HttpUtil.encode(value.toString())); + } + } + } + + //~--- fields --------------------------------------------------------------- + + /** content builder */ + private final StringBuilder builder = new StringBuilder(); + + /** request */ + private final AdvancedHttpRequestWithBody request; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java b/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java new file mode 100644 index 0000000000..41a23dcccb --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/HttpMethod.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +/** + * Http methods. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public final class HttpMethod +{ + + /** http delete method */ + public static final String DELETE = "DELETE"; + + /** http get method */ + public static final String GET = "GET"; + + /** http head method */ + public static final String HEAD = "HEAD"; + + /** http options method */ + public static final String OPTIONS = "OPTIONS"; + + /** http post method */ + public static final String POST = "POST"; + + /** http put method */ + public static final String PUT = "PUT"; + + //~--- constructors --------------------------------------------------------- + + private HttpMethod() {} +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java new file mode 100644 index 0000000000..0b5e99bc49 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/RawContent.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.annotations.VisibleForTesting; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Byte array content for {@link AdvancedHttpRequestWithBody}. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class RawContent implements Content +{ + + /** + * Constructs a new {@link RawContent}. + * + * + * @param data data + */ + public RawContent(byte[] data) + { + this.data = data; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Sets the length of the byte array as http header. + * + * + * @param request request + */ + @Override + public void prepare(AdvancedHttpRequestWithBody request) + { + request.contentLength(data.length); + } + + /** + * Writes the byte array to the output stream. + * + * + * @param output output stream + * + * @throws IOException + */ + @Override + public void process(OutputStream output) throws IOException + { + output.write(data); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns content data. + * + * + * @return content data + */ + @VisibleForTesting + byte[] getData() + { + return data; + } + + //~--- fields --------------------------------------------------------------- + + /** byte array */ + private final byte[] data; +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java b/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java new file mode 100644 index 0000000000..07473447a7 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/StringContent.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- JDK imports ------------------------------------------------------------ + +import java.nio.charset.Charset; + +/** + * String content for {@link AdvancedHttpRequestWithBody}. + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class StringContent extends RawContent +{ + + /** + * Constructs a new {@link StringContent}. + * + * + * @param content content + * @param charset charset + */ + public StringContent(String content, Charset charset) + { + super(content.getBytes(charset)); + } +} diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java b/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java new file mode 100644 index 0000000000..d11a603b5a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/net/ahc/package-info.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +/** + * Advanced http client. + */ +package sonia.scm.net.ahc; diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java new file mode 100644 index 0000000000..18b291d8ad --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class AdvancedHttpClientTest { + + private final AdvancedHttpClient client = new AdvancedHttpClient() + { + + @Override + protected AdvancedHttpResponse request( + BaseHttpRequest request) throws IOException + { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + }; + + private static final String URL = "https://www.scm-manager.org"; + + @Test + public void testGet() + { + AdvancedHttpRequest request = client.get(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.GET, request.getMethod()); + } + + @Test + public void testDelete() + { + AdvancedHttpRequestWithBody request = client.delete(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.DELETE, request.getMethod()); + } + + @Test + public void testPut() + { + AdvancedHttpRequestWithBody request = client.put(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.PUT, request.getMethod()); + } + + @Test + public void testPost() + { + AdvancedHttpRequestWithBody request = client.post(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.POST, request.getMethod()); + } + + @Test + public void testOptions() + { + AdvancedHttpRequestWithBody request = client.options(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.OPTIONS, request.getMethod()); + } + + @Test + public void testHead() + { + AdvancedHttpRequest request = client.head(URL); + assertEquals(URL, request.getUrl()); + assertEquals(HttpMethod.HEAD, request.getMethod()); + } +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java new file mode 100644 index 0000000000..5b2d675e97 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class AdvancedHttpRequestTest { + + @Mock + private AdvancedHttpClient ahc; + + @Test + public void testSelf() + { + AdvancedHttpRequest ahr = new AdvancedHttpRequest(ahc, HttpMethod.GET, "https://www.scm-manager.org"); + assertEquals(AdvancedHttpRequest.class, ahr.self().getClass()); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java new file mode 100644 index 0000000000..8e656133e7 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import java.io.File; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class AdvancedHttpRequestWithBodyTest { + + @Mock + private AdvancedHttpClient ahc; + + private AdvancedHttpRequestWithBody request; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Before + public void before(){ + request = new AdvancedHttpRequestWithBody(ahc, HttpMethod.PUT, "https://www.scm-manager.org"); + } + + @Test + public void testContentLength() + { + request.contentLength(12l); + assertEquals("12", request.getHeaders().get("Content-Length").iterator().next()); + } + + @Test + public void testContentType(){ + request.contentType("text/plain"); + assertEquals("text/plain", request.getHeaders().get("Content-Type").iterator().next()); + } + + @Test + public void testFileContent() throws IOException{ + File file = tempFolder.newFile(); + request.fileContent(file); + assertThat(request.getContent(), instanceOf(FileContent.class)); + } + + @Test + public void testRawContent() throws IOException { + request.rawContent("test".getBytes(Charsets.UTF_8)); + assertThat(request.getContent(), instanceOf(RawContent.class)); + } + + @Test + public void testRawContentWithByteSource() throws IOException { + ByteSource bs = ByteSource.wrap("test".getBytes(Charsets.UTF_8)); + request.rawContent(bs); + assertThat(request.getContent(), instanceOf(ByteSourceContent.class)); + } + + @Test + public void testFormContent(){ + FormContentBuilder builder = request.formContent(); + assertNotNull(builder); + builder.build(); + assertThat(request.getContent(), instanceOf(StringContent.class)); + } + + @Test + public void testStringContent(){ + request.stringContent("test"); + assertThat(request.getContent(), instanceOf(StringContent.class)); + } + + @Test + public void testStringContentWithCharset(){ + request.stringContent("test", Charsets.UTF_8); + assertThat(request.getContent(), instanceOf(StringContent.class)); + } + + @Test + public void testSelf() + { + assertEquals(AdvancedHttpRequestWithBody.class, request.self().getClass()); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java new file mode 100644 index 0000000000..9b3ccaa348 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class AdvancedHttpResponseTest { + + @Mock(answer = Answers.CALLS_REAL_METHODS) + private AdvancedHttpResponse response; + + @Test + public void testContent() throws IOException + { + ByteSource bs = ByteSource.wrap("test123".getBytes(Charsets.UTF_8)); + when(response.contentAsByteSource()).thenReturn(bs); + byte[] data = response.content(); + assertEquals("test123", new String(data, Charsets.UTF_8)); + } + + @Test + public void testContentAsString() throws IOException + { + ByteSource bs = ByteSource.wrap("123test".getBytes(Charsets.UTF_8)); + when(response.contentAsByteSource()).thenReturn(bs); + assertEquals("123test", response.contentAsString()); + } + + @Test + public void testContentAsReader() throws IOException + { + ByteSource bs = ByteSource.wrap("abc123".getBytes(Charsets.UTF_8)); + when(response.contentAsByteSource()).thenReturn(bs); + assertEquals("abc123", CharStreams.toString(response.contentAsReader())); + } + + @Test + public void testContentAsStream() throws IOException + { + ByteSource bs = ByteSource.wrap("cde456".getBytes(Charsets.UTF_8)); + when(response.contentAsByteSource()).thenReturn(bs); + byte[] data = ByteStreams.toByteArray(response.contentAsStream()); + assertEquals("cde456", new String(data, Charsets.UTF_8)); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java new file mode 100644 index 0000000000..7adab5aa84 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import java.io.IOException; +import java.util.Collection; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class BaseHttpRequestTest { + + @Mock + private AdvancedHttpClient ahc; + + private BaseHttpRequest request; + + @Before + public void before(){ + request = new AdvancedHttpRequest(ahc, HttpMethod.GET, "https://www.scm-manager.org"); + } + + @Test + public void testBasicAuth() + { + request.basicAuth("tricia", "mcmillian123"); + Multimap headers = request.getHeaders(); + assertEquals("Basic dHJpY2lhOm1jbWlsbGlhbjEyMw==", headers.get("Authorization").iterator().next()); + } + + @Test + public void testQueryString(){ + request.queryString("a", "b"); + assertEquals("https://www.scm-manager.org?a=b", request.getUrl()); + } + + @Test + public void testQueryStringMultiple(){ + request.queryString("a", "b"); + request.queryString("c", "d", "e"); + assertEquals("https://www.scm-manager.org?a=b&c=d&c=e", request.getUrl()); + } + + @Test + public void testQueryStringEncoded(){ + request.queryString("a", "äüö"); + assertEquals("https://www.scm-manager.org?a=%C3%A4%C3%BC%C3%B6", request.getUrl()); + } + + @Test + public void testQueryStrings(){ + Iterable i1 = Lists.newArrayList("b"); + Iterable i2 = Lists.newArrayList("d", "e"); + request.queryStrings("a", i1); + request.queryStrings("c", i2); + assertEquals("https://www.scm-manager.org?a=b&c=d&c=e", request.getUrl()); + } + + @Test + public void testQuerqStringNullValue(){ + request.queryString("a", null, "b"); + assertEquals("https://www.scm-manager.org?a=&a=b", request.getUrl()); + } + + @Test + public void testHeader(){ + request.header("a", "b"); + assertEquals("b", request.getHeaders().get("a").iterator().next()); + } + + @Test + public void testHeaderMultiple(){ + request.header("a", "b", "c", "d"); + Collection values = request.getHeaders().get("a"); + assertThat(values, containsInAnyOrder("b", "c", "d")); + } + + @Test + public void testRequest() throws IOException{ + request.request(); + verify(ahc).request(request); + } + + @Test + public void testBuilderMethods(){ + Iterable i1 = Lists.newArrayList("b"); + assertThat(request.decodeGZip(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDecodeGZip()); + assertThat(request.disableCertificateValidation(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDisableCertificateValidation()); + assertThat(request.disableHostnameValidation(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDisableHostnameValidation()); + assertThat(request.ignoreProxySettings(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isIgnoreProxySettings()); + assertThat(request.header("a", "b"), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.headers("a", i1), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.queryString("a", "b"), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.queryStrings("a", i1), instanceOf(AdvancedHttpRequest.class)); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java new file mode 100644 index 0000000000..24487f6582 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class ByteSourceContentTest { + + @Mock + private AdvancedHttpRequestWithBody request; + + @Test + public void testPrepareRequest() throws IOException + { + ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8)); + ByteSourceContent content = new ByteSourceContent(source); + content.prepare(request); + verify(request).contentLength(3l); + } + + @Test + public void testProcess() throws IOException{ + ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8)); + ByteSourceContent content = new ByteSourceContent(source); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java new file mode 100644 index 0000000000..95b381b880 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.io.Files; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class FileContentTest +{ + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testPrepareRequest() throws IOException + { + FileContent content = create("abc"); + + content.prepare(request); + verify(request).contentLength(3l); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testProcess() throws IOException + { + FileContent content = create("abc"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + content.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + + private FileContent create(String value) throws IOException + { + File file = temp.newFile(); + + Files.write("abc", file, Charsets.UTF_8); + + return new FileContent(file); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + /** Field description */ + @Mock + private AdvancedHttpRequestWithBody request; +} diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java new file mode 100644 index 0000000000..56cc6d7379 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.collect.Lists; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class FormContentBuilderTest { + + @Mock + private AdvancedHttpRequestWithBody request; + + @InjectMocks + private FormContentBuilder builder; + + @Test + public void testFieldEncoding() + { + builder.field("a", "ü", "ä", "ö").build(); + assertContent("a=%C3%BC&a=%C3%A4&a=%C3%B6"); + } + + @Test + public void testBuild() + { + builder.field("a", "b").build(); + assertContent("a=b"); + verify(request).contentType("application/x-www-form-urlencoded"); + } + + @Test + public void testFieldWithArray() + { + builder.field("a", "b").field("c", "d", "e").build(); + assertContent("a=b&c=d&c=e"); + } + + @Test + public void testFieldWithIterable() + { + Iterable i1 = Lists.newArrayList("b"); + builder.fields("a", i1) + .fields("c", Lists.newArrayList("d", "e")) + .build(); + assertContent("a=b&c=d&c=e"); + } + + private void assertContent(String content){ + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(request).stringContent(captor.capture()); + assertEquals(content, captor.getValue()); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java new file mode 100644 index 0000000000..f4ee8b7031 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class RawContentTest { + + + @Mock + private AdvancedHttpRequestWithBody request; + + @Test + public void testPrepareRequest() + { + RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8)); + raw.prepare(request); + verify(request).contentLength(3); + } + + @Test + public void testProcess() throws IOException + { + RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + raw.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java new file mode 100644 index 0000000000..bd191438ca --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class StringContentTest { + + + @Test + public void testStringContent() + { + StringContent sc = new StringContent("abc", Charsets.UTF_8); + assertEquals("abc", new String(sc.getData())); + } + + @Test + public void testStringContentWithCharset() + { + StringContent sc = new StringContent("üäö", Charsets.ISO_8859_1); + assertEquals("üäö", new String(sc.getData(), Charsets.ISO_8859_1)); + } + +} \ No newline at end of file diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java new file mode 100644 index 0000000000..1aadef143c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java @@ -0,0 +1,319 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Strings; +import com.google.common.collect.Multimap; +import com.google.common.io.Closeables; +import com.google.inject.Inject; + +import org.apache.shiro.codec.Base64; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.config.ScmConfiguration; +import sonia.scm.net.Proxies; +import sonia.scm.net.TrustAllHostnameVerifier; +import sonia.scm.net.TrustAllTrustManager; +import sonia.scm.util.HttpUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.OutputStream; + +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.ProtocolException; +import java.net.Proxy; +import java.net.SocketAddress; +import java.net.URL; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class DefaultAdvancedHttpClient extends AdvancedHttpClient +{ + + /** Field description */ + public static final String CREDENTIAL_SEPARATOR = ":"; + + /** Field description */ + public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** Field description */ + public static final String PREFIX_BASIC_AUTHENTICATION = "Basic "; + + /** Field description */ + public static final int TIMEOUT_CONNECTION = 30000; + + /** Field description */ + public static final int TIMEOUT_RAED = 1200000; + + /** + * the logger for DefaultAdvancedHttpClient + */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultAdvancedHttpClient.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configuration + */ + @Inject + public DefaultAdvancedHttpClient(ScmConfiguration configuration) + { + this.configuration = configuration; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + * + * @throws IOException + */ + @Override + protected AdvancedHttpResponse request(BaseHttpRequest request) + throws IOException + { + HttpURLConnection connection = openConnection(request, + new URL(request.getUrl())); + + applyBaseSettings(request, connection); + + if (connection instanceof HttpsURLConnection) + { + applySSLSettings(request, (HttpsURLConnection) connection); + } + + Content content = null; + + if (request instanceof AdvancedHttpRequestWithBody) + { + AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request; + + content = ahrwb.getContent(); + + if (content != null) + { + content.prepare(ahrwb); + } + else + { + request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); + } + } + else + { + request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); + } + + applyHeaders(request, connection); + + if (content != null) + { + applyContent(connection, content); + } + + return new DefaultAdvancedHttpResponse(connection, + connection.getResponseCode(), connection.getResponseMessage()); + } + + private void appendProxyAuthentication(HttpURLConnection connection) + { + String username = configuration.getProxyUser(); + String password = configuration.getProxyPassword(); + + if (!Strings.isNullOrEmpty(username) ||!Strings.isNullOrEmpty(password)) + { + logger.debug("append proxy authentication header for user {}", username); + + String auth = username.concat(CREDENTIAL_SEPARATOR).concat(password); + + auth = Base64.encodeToString(auth.getBytes()); + connection.addRequestProperty(HEADER_PROXY_AUTHORIZATION, + PREFIX_BASIC_AUTHENTICATION.concat(auth)); + } + } + + private void applyBaseSettings(BaseHttpRequest request, + HttpURLConnection connection) + throws ProtocolException + { + connection.setRequestMethod(request.getMethod()); + connection.setReadTimeout(TIMEOUT_RAED); + connection.setConnectTimeout(TIMEOUT_CONNECTION); + } + + private void applyContent(HttpURLConnection connection, Content content) + throws IOException + { + connection.setDoOutput(true); + + OutputStream output = null; + + try + { + output = connection.getOutputStream(); + content.process(output); + } + finally + { + Closeables.close(output, true); + } + } + + private void applyHeaders(BaseHttpRequest request, + HttpURLConnection connection) + { + Multimap headers = request.getHeaders(); + + for (String key : headers.keySet()) + { + for (String value : headers.get(key)) + { + connection.setRequestProperty(key, value); + } + } + } + + private void applySSLSettings(BaseHttpRequest request, + HttpsURLConnection connection) + { + if (request.isDisableCertificateValidation()) + { + logger.trace("disable certificate validation"); + + try + { + TrustManager[] trustAllCerts = new TrustManager[] { + new TrustAllTrustManager() }; + SSLContext sc = SSLContext.getInstance("SSL"); + + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + connection.setSSLSocketFactory(sc.getSocketFactory()); + } + catch (KeyManagementException ex) + { + logger.error("could not disable certificate validation", ex); + } + catch (NoSuchAlgorithmException ex) + { + logger.error("could not disable certificate validation", ex); + } + } + + if (request.isDisableHostnameValidation()) + { + logger.trace("disable hostname validation"); + connection.setHostnameVerifier(new TrustAllHostnameVerifier()); + } + } + + private HttpURLConnection openConnection(BaseHttpRequest request, URL url) + throws IOException + { + HttpURLConnection connection; + + if (isProxyEnabled(request)) + { + connection = openProxyConnection(request, url); + appendProxyAuthentication(connection); + } + else + { + if (request.isIgnoreProxySettings()) + { + logger.trace("ignore proxy settings"); + } + + logger.debug("fetch {}", url.toExternalForm()); + + connection = (HttpURLConnection) url.openConnection(); + } + + return connection; + } + + private HttpURLConnection openProxyConnection(BaseHttpRequest request, + URL url) + throws IOException + { + if (logger.isDebugEnabled()) + { + logger.debug("fetch '{}' using proxy {}:{}", url.toExternalForm(), + configuration.getProxyServer(), configuration.getProxyPort()); + } + + SocketAddress address = + new InetSocketAddress(configuration.getProxyServer(), + configuration.getProxyPort()); + + return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, + address)); + } + + //~--- get methods ---------------------------------------------------------- + + private boolean isProxyEnabled(BaseHttpRequest request) + { + return !request.isIgnoreProxySettings() + && Proxies.isEnabled(configuration, request.getUrl()); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final ScmConfiguration configuration; +} diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java new file mode 100644 index 0000000000..a679995a44 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteSource; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; +import java.io.InputStream; + +import java.net.HttpURLConnection; + +import java.util.List; +import java.util.Map.Entry; + +/** + * + * @author Sebastian Sdorra + * @since 1.46 + */ +public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse +{ + + /** + * Constructs ... + * + * + * @param connection + * @param status + * @param statusText + */ + DefaultAdvancedHttpResponse(HttpURLConnection connection, int status, + String statusText) + { + this.connection = connection; + this.status = status; + this.statusText = statusText; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + * + * @throws IOException + */ + @Override + public ByteSource contentAsByteSource() throws IOException + { + return new URLConnectionByteSource(connection); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + @Override + public Multimap getHeaders() + { + if (headers == null) + { + headers = HashMultimap.create(); + + for (Entry> e : + connection.getHeaderFields().entrySet()) + { + headers.putAll(e.getKey(), e.getValue()); + } + } + + return headers; + } + + /** + * Method description + * + * + * @return + */ + @Override + public int getStatus() + { + return status; + } + + /** + * Method description + * + * + * @return + */ + @Override + public String getStatusText() + { + return statusText; + } + + //~--- inner classes -------------------------------------------------------- + + /** + * {@link ByteSource} implementation of a http connection. + */ + private static class URLConnectionByteSource extends ByteSource + { + + /** + * Constructs a new {@link URLConnectionByteSource}. + * + * + * @param connection http connection + */ + private URLConnectionByteSource(HttpURLConnection connection) + { + this.connection = connection; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Opens the http connection. + * + * + * @return http connection + * + * @throws IOException + */ + @Override + public InputStream openStream() throws IOException + { + return connection.getInputStream(); + } + + //~--- fields ------------------------------------------------------------- + + /** http connection */ + private final HttpURLConnection connection; + } + + + //~--- fields --------------------------------------------------------------- + + /** http connection */ + private final HttpURLConnection connection; + + /** Field description */ + private final int status; + + /** Field description */ + private final String statusText; + + /** http headers */ + private Multimap headers; +} From 3c6306059f35b009a3ddb97a2b6f0d14cb13dc78 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 1 May 2015 12:36:49 +0200 Subject: [PATCH 2/7] more unit tests for ahc --- .../sonia/scm/net/ahc/BaseHttpRequest.java | 3 +- .../scm/net/ahc/AdvancedHttpResponseTest.java | 39 ++ .../scm/net/ahc/BaseHttpRequestTest.java | 2 +- .../net/ahc/DefaultAdvancedHttpClient.java | 83 ++++- .../net/ahc/DefaultAdvancedHttpResponse.java | 38 +- .../ahc/DefaultAdvancedHttpClientTest.java | 352 ++++++++++++++++++ .../ahc/DefaultAdvancedHttpResponseTest.java | 121 ++++++ 7 files changed, 591 insertions(+), 47 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java index f48e819ddc..c80ae9b1c4 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java @@ -36,6 +36,7 @@ package sonia.scm.net.ahc; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import org.apache.shiro.codec.Base64; @@ -386,7 +387,7 @@ public abstract class BaseHttpRequest protected final AdvancedHttpClient client; /** http header */ - private final Multimap headers = HashMultimap.create(); + private final Multimap headers = LinkedHashMultimap.create(); /** http method */ private final String method; diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java index 9b3ccaa348..277bda856d 100644 --- a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java +++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java @@ -32,6 +32,8 @@ package sonia.scm.net.ahc; import com.google.common.base.Charsets; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; import com.google.common.io.ByteSource; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; @@ -87,5 +89,42 @@ public class AdvancedHttpResponseTest { byte[] data = ByteStreams.toByteArray(response.contentAsStream()); assertEquals("cde456", new String(data, Charsets.UTF_8)); } + + @Test + public void testGetFirstHeader() throws IOException + { + Multimap mm = LinkedHashMultimap.create(); + mm.put("Test", "One"); + mm.put("Test-2", "One"); + mm.put("Test-2", "Two"); + when(response.getHeaders()).thenReturn(mm); + assertEquals("One", response.getFirstHeader("Test")); + assertEquals("One", response.getFirstHeader("Test-2")); + assertNull(response.getFirstHeader("Test-3")); + } + + @Test + public void testIsSuccessful() throws IOException + { + // successful + when(response.getStatus()).thenReturn(200); + assertTrue(response.isSuccessful()); + when(response.getStatus()).thenReturn(201); + assertTrue(response.isSuccessful()); + when(response.getStatus()).thenReturn(204); + assertTrue(response.isSuccessful()); + when(response.getStatus()).thenReturn(301); + assertTrue(response.isSuccessful()); + + // not successful + when(response.getStatus()).thenReturn(400); + assertFalse(response.isSuccessful()); + when(response.getStatus()).thenReturn(404); + assertFalse(response.isSuccessful()); + when(response.getStatus()).thenReturn(500); + assertFalse(response.isSuccessful()); + when(response.getStatus()).thenReturn(199); + assertFalse(response.isSuccessful()); + } } \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java index 7adab5aa84..9b3ce995f5 100644 --- a/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java +++ b/scm-core/src/test/java/sonia/scm/net/ahc/BaseHttpRequestTest.java @@ -113,7 +113,7 @@ public class BaseHttpRequestTest { public void testHeaderMultiple(){ request.header("a", "b", "c", "d"); Collection values = request.getHeaders().get("a"); - assertThat(values, containsInAnyOrder("b", "c", "d")); + assertThat(values, contains("b", "c", "d")); } @Test diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java index 1aadef143c..6b80d6bdae 100644 --- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java @@ -33,6 +33,7 @@ package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.Multimap; import com.google.common.io.Closeables; @@ -69,6 +70,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; /** + * Default implementation of the {@link AdvancedHttpClient}. The default + * implementation uses {@link HttpURLConnection}. * * @author Sebastian Sdorra * @since 1.46 @@ -76,20 +79,23 @@ import javax.net.ssl.TrustManager; public class DefaultAdvancedHttpClient extends AdvancedHttpClient { - /** Field description */ - public static final String CREDENTIAL_SEPARATOR = ":"; + /** credential separator */ + private static final String CREDENTIAL_SEPARATOR = ":"; - /** Field description */ - public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; + /** proxy authorization header */ + @VisibleForTesting + static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; - /** Field description */ - public static final String PREFIX_BASIC_AUTHENTICATION = "Basic "; + /** basic authentication prefix */ + private static final String PREFIX_BASIC_AUTHENTICATION = "Basic "; - /** Field description */ - public static final int TIMEOUT_CONNECTION = 30000; + /** connection timeout */ + @VisibleForTesting + static final int TIMEOUT_CONNECTION = 30000; - /** Field description */ - public static final int TIMEOUT_RAED = 1200000; + /** read timeout */ + @VisibleForTesting + static final int TIMEOUT_RAED = 1200000; /** * the logger for DefaultAdvancedHttpClient @@ -100,10 +106,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient //~--- constructors --------------------------------------------------------- /** - * Constructs ... + * Constructs a new {@link DefaultAdvancedHttpClient}. * * - * @param configuration + * @param configuration scm-manager main configuration */ @Inject public DefaultAdvancedHttpClient(ScmConfiguration configuration) @@ -114,12 +120,50 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient //~--- methods -------------------------------------------------------------- /** - * Method description + * Creates a new {@link HttpURLConnection} from the given {@link URL}. The + * method is visible for testing. * * - * @param request + * @param url url * - * @return + * @return new {@link HttpURLConnection} + * + * @throws IOException + */ + @VisibleForTesting + protected HttpURLConnection createConnection(URL url) throws IOException + { + return (HttpURLConnection) url.openConnection(); + } + + /** + * Creates a new proxy {@link HttpURLConnection} from the given {@link URL} + * and {@link SocketAddress}. The method is visible for testing. + * + * + * @param url url + * @param address proxy socket address + * + * @return new proxy {@link HttpURLConnection} + * + * @throws IOException + */ + @VisibleForTesting + protected HttpURLConnection createProxyConnecton(URL url, + SocketAddress address) + throws IOException + { + return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, + address)); + } + + /** + * Executes the given request and returns the server response. + * + * + * @param request http request + * + * @return server response * * @throws IOException */ @@ -223,7 +267,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient { for (String value : headers.get(key)) { - connection.setRequestProperty(key, value); + connection.addRequestProperty(key, value); } } } @@ -280,7 +324,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient logger.debug("fetch {}", url.toExternalForm()); - connection = (HttpURLConnection) url.openConnection(); + connection = createConnection(url); } return connection; @@ -300,8 +344,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient new InetSocketAddress(configuration.getProxyServer(), configuration.getProxyPort()); - return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, - address)); + return createProxyConnecton(url, address); } //~--- get methods ---------------------------------------------------------- @@ -314,6 +357,6 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient //~--- fields --------------------------------------------------------------- - /** Field description */ + /** scm-manager main configuration */ private final ScmConfiguration configuration; } diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java index a679995a44..d5eb7d1727 100644 --- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java @@ -34,6 +34,7 @@ package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.io.ByteSource; @@ -48,6 +49,7 @@ import java.util.List; import java.util.Map.Entry; /** + * Http server response object of {@link DefaultAdvancedHttpClient}. * * @author Sebastian Sdorra * @since 1.46 @@ -56,12 +58,12 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse { /** - * Constructs ... + * Constructs a new {@link DefaultAdvancedHttpResponse}. * * - * @param connection - * @param status - * @param statusText + * @param connection http connection + * @param status response status code + * @param statusText response status text */ DefaultAdvancedHttpResponse(HttpURLConnection connection, int status, String statusText) @@ -74,12 +76,7 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @return - * - * @throws IOException + * {@inheritDoc} */ @Override public ByteSource contentAsByteSource() throws IOException @@ -90,17 +87,14 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse //~--- get methods ---------------------------------------------------------- /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public Multimap getHeaders() { if (headers == null) { - headers = HashMultimap.create(); + headers = LinkedHashMultimap.create(); for (Entry> e : connection.getHeaderFields().entrySet()) @@ -113,10 +107,7 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int getStatus() @@ -125,10 +116,7 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String getStatusText() @@ -183,10 +171,10 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse /** http connection */ private final HttpURLConnection connection; - /** Field description */ + /** server response status */ private final int status; - /** Field description */ + /** server response text */ private final String statusText; /** http headers */ diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java new file mode 100644 index 0000000000..451c07b5cc --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java @@ -0,0 +1,352 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import sonia.scm.config.ScmConfiguration; +import sonia.scm.net.TrustAllHostnameVerifier; +import sonia.scm.util.HttpUtil; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import java.net.HttpURLConnection; +import java.net.SocketAddress; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultAdvancedHttpClientTest +{ + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testApplyBaseSettings() throws IOException + { + new AdvancedHttpRequest(client, HttpMethod.GET, + "https://www.scm-manager.org").request(); + verify(connection).setRequestMethod(HttpMethod.GET); + verify(connection).setReadTimeout(DefaultAdvancedHttpClient.TIMEOUT_RAED); + verify(connection).setConnectTimeout( + DefaultAdvancedHttpClient.TIMEOUT_CONNECTION); + verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0"); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testApplyContent() throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + when(connection.getOutputStream()).thenReturn(baos); + + AdvancedHttpRequestWithBody request = + new AdvancedHttpRequestWithBody(client, HttpMethod.PUT, + "https://www.scm-manager.org"); + + request.stringContent("test").request(); + verify(connection).setDoOutput(true); + verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "4"); + assertEquals("test", baos.toString("UTF-8")); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testApplyHeaders() throws IOException + { + AdvancedHttpRequest request = new AdvancedHttpRequest(client, + HttpMethod.POST, + "http://www.scm-manager.org"); + + request.header("Header-One", "One").header("Header-Two", "Two").request(); + verify(connection).setRequestMethod(HttpMethod.POST); + verify(connection).addRequestProperty("Header-One", "One"); + verify(connection).addRequestProperty("Header-Two", "Two"); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testApplyMultipleHeaders() throws IOException + { + AdvancedHttpRequest request = new AdvancedHttpRequest(client, + HttpMethod.POST, + "http://www.scm-manager.org"); + + request.header("Header-One", "One").header("Header-One", "Two").request(); + verify(connection).setRequestMethod(HttpMethod.POST); + verify(connection).addRequestProperty("Header-One", "One"); + verify(connection).addRequestProperty("Header-One", "Two"); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testBodyRequestWithoutContent() throws IOException + { + AdvancedHttpRequestWithBody request = + new AdvancedHttpRequestWithBody(client, HttpMethod.PUT, + "https://www.scm-manager.org"); + + request.request(); + verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0"); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testDisableCertificateValidation() throws IOException + { + AdvancedHttpRequest request = new AdvancedHttpRequest(client, + HttpMethod.GET, + "https://www.scm-manager.org"); + + request.disableCertificateValidation(true).request(); + verify(connection).setSSLSocketFactory(any(SSLSocketFactory.class)); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testDisableHostnameValidation() throws IOException + { + AdvancedHttpRequest request = new AdvancedHttpRequest(client, + HttpMethod.GET, + "https://www.scm-manager.org"); + + request.disableHostnameValidation(true).request(); + verify(connection).setHostnameVerifier(any(TrustAllHostnameVerifier.class)); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testIgnoreProxy() throws IOException + { + configuration.setProxyServer("proxy.scm-manager.org"); + configuration.setProxyPort(8090); + configuration.setEnableProxy(true); + new AdvancedHttpRequest(client, HttpMethod.GET, + "https://www.scm-manager.org").ignoreProxySettings(true).request(); + assertFalse(client.proxyConnection); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testProxyConnection() throws IOException + { + configuration.setProxyServer("proxy.scm-manager.org"); + configuration.setProxyPort(8090); + configuration.setEnableProxy(true); + new AdvancedHttpRequest(client, HttpMethod.GET, + "https://www.scm-manager.org").request(); + assertTrue(client.proxyConnection); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testProxyWithAuthentication() throws IOException + { + configuration.setProxyServer("proxy.scm-manager.org"); + configuration.setProxyPort(8090); + configuration.setProxyUser("tricia"); + configuration.setProxyPassword("tricias secret"); + configuration.setEnableProxy(true); + new AdvancedHttpRequest(client, HttpMethod.GET, + "https://www.scm-manager.org").request(); + assertTrue(client.proxyConnection); + verify(connection).addRequestProperty( + DefaultAdvancedHttpClient.HEADER_PROXY_AUTHORIZATION, + "Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0"); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void setUp() + { + configuration = new ScmConfiguration(); + client = new TestingAdvacedHttpClient(configuration); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 15/05/01 + * @author Enter your name here... + */ + public class TestingAdvacedHttpClient extends DefaultAdvancedHttpClient + { + + /** + * Constructs ... + * + * + * @param configuration + */ + public TestingAdvacedHttpClient(ScmConfiguration configuration) + { + super(configuration); + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + * + * @param url + * + * @return + * + * @throws IOException + */ + @Override + protected HttpURLConnection createConnection(URL url) throws IOException + { + return connection; + } + + /** + * Method description + * + * + * @param url + * @param address + * + * @return + * + * @throws IOException + */ + @Override + protected HttpURLConnection createProxyConnecton(URL url, + SocketAddress address) + throws IOException + { + proxyConnection = true; + + return connection; + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private boolean proxyConnection = false; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private TestingAdvacedHttpClient client; + + /** Field description */ + private ScmConfiguration configuration; + + /** Field description */ + @Mock + private HttpsURLConnection connection; +} diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java new file mode 100644 index 0000000000..470f1d14ae --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.*; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import java.net.HttpURLConnection; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultAdvancedHttpResponseTest +{ + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testContentAsByteSource() throws IOException + { + ByteArrayInputStream bais = + new ByteArrayInputStream("test".getBytes(Charsets.UTF_8)); + + when(connection.getInputStream()).thenReturn(bais); + + AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection, + 200, "OK"); + ByteSource content = response.contentAsByteSource(); + + assertEquals("test", content.asCharSource(Charsets.UTF_8).read()); + } + + /** + * Method description + * + */ + @Test + public void testGetHeaders() + { + LinkedHashMap> map = Maps.newLinkedHashMap(); + List test = Lists.newArrayList("One", "Two"); + + map.put("Test", test); + when(connection.getHeaderFields()).thenReturn(map); + + AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection, + 200, "OK"); + Multimap headers = response.getHeaders(); + + assertThat(headers.get("Test"), contains("One", "Two")); + assertTrue(headers.get("Test-2").isEmpty()); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Mock + private HttpURLConnection connection; +} From 722d2616a898f1699db0dc6b282b299e2d8cd225 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 1 May 2015 14:21:15 +0200 Subject: [PATCH 3/7] return error stream, if input stream could not be returned --- .../net/ahc/DefaultAdvancedHttpResponse.java | 36 ++++++++++++++++--- .../ahc/DefaultAdvancedHttpResponseTest.java | 22 ++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java index d5eb7d1727..c42e36deb5 100644 --- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java @@ -33,11 +33,13 @@ package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.collect.HashMultimap; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.io.ByteSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; @@ -132,6 +134,14 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse private static class URLConnectionByteSource extends ByteSource { + /** + * the logger for URLConnectionByteSource + */ + private static final Logger logger = + LoggerFactory.getLogger(URLConnectionByteSource.class); + + //~--- constructors ------------------------------------------------------- + /** * Constructs a new {@link URLConnectionByteSource}. * @@ -146,17 +156,35 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse //~--- methods ------------------------------------------------------------ /** - * Opens the http connection. + * Opens the input stream of http connection, if an error occurs during + * opening the method will return the error stream instead. * * - * @return http connection + * @return input or error stream of http connection * * @throws IOException */ @Override public InputStream openStream() throws IOException { - return connection.getInputStream(); + InputStream stream; + + try + { + stream = connection.getInputStream(); + } + catch (IOException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "could not open input stream, open error stream instead", ex); + } + + stream = connection.getErrorStream(); + } + + return stream; } //~--- fields ------------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java index 470f1d14ae..063b8b6f09 100644 --- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java @@ -92,6 +92,28 @@ public class DefaultAdvancedHttpResponseTest assertEquals("test", content.asCharSource(Charsets.UTF_8).read()); } + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testContentAsByteSourceWithFailedRequest() throws IOException + { + ByteArrayInputStream bais = + new ByteArrayInputStream("test".getBytes(Charsets.UTF_8)); + + when(connection.getInputStream()).thenThrow(IOException.class); + when(connection.getErrorStream()).thenReturn(bais); + + AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection, + 404, "NOT FOUND"); + ByteSource content = response.contentAsByteSource(); + + assertEquals("test", content.asCharSource(Charsets.UTF_8).read()); + } + /** * Method description * From 1f4524bb208375c62ac8129eba84f6eb54cbe5b5 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 3 May 2015 15:51:21 +0200 Subject: [PATCH 4/7] added json and xml support to ahc --- .../sonia/scm/net/ahc/AdvancedHttpClient.java | 31 ++- .../net/ahc/AdvancedHttpRequestWithBody.java | 55 ++++++ .../scm/net/ahc/AdvancedHttpResponse.java | 187 ++++++++++++++---- .../sonia/scm/net/ahc/ContentTransformer.java | 83 ++++++++ .../net/ahc/ContentTransformerException.java | 72 +++++++ .../ContentTransformerNotFoundException.java | 60 ++++++ .../java/sonia/scm/net/ahc/ContentType.java | 52 +++++ .../scm/net/ahc/AdvancedHttpClientTest.java | 26 +-- .../ahc/AdvancedHttpRequestWithBodyTest.java | 42 ++++ .../scm/net/ahc/AdvancedHttpResponseTest.java | 84 ++++++++ .../main/java/sonia/scm/ScmServletModule.java | 14 +- .../net/ahc/DefaultAdvancedHttpClient.java | 56 +++++- .../net/ahc/DefaultAdvancedHttpResponse.java | 22 ++- .../scm/net/ahc/JsonContentTransformer.java | 149 ++++++++++++++ .../scm/net/ahc/XmlContentTransformer.java | 134 +++++++++++++ .../ahc/DefaultAdvancedHttpClientTest.java | 31 ++- .../ahc/DefaultAdvancedHttpResponseTest.java | 20 +- .../net/ahc/JsonContentTransformerTest.java | 93 +++++++++ .../net/ahc/XmlContentTransformerTest.java | 92 +++++++++ 19 files changed, 1222 insertions(+), 81 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java create mode 100644 scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java create mode 100644 scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java create mode 100644 scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java create mode 100644 scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java index 943d1a0352..aa660f21ca 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java @@ -39,7 +39,7 @@ import java.io.IOException; * Advanced client for http operations. The {@link AdvancedHttpClient} replaces * the much more simpler implementation {@link sonia.scm.net.HttpClient}. The * {@link AdvancedHttpClient} offers a fluid interface for handling most common - * http operations. The {@link AdvancedHttpClient} can be injected by the + * http operations. The {@link AdvancedHttpClient} can be injected by the * default injection mechanism of SCM-Manager. *

 

* Http GET example: @@ -48,13 +48,13 @@ import java.io.IOException; * AdvancedHttpResponse response = client.get("https://www.scm-manager.org") * .decodeGZip(true) * .request(); - * + * * System.out.println(response.contentAsString()); * - * + * *

 

* Http POST example: - * + * *

  * AdvancedHttpResponse response = client.post("https://www.scm-manager.org")
  *                                       .formContent()
@@ -62,7 +62,7 @@ import java.io.IOException;
  *                                       .field("lastname", "McMillan")
  *                                       .build()
  *                                       .request();
- * 
+ *
  * if (response.isSuccessful()){
  *   System.out.println("success");
  * }
@@ -70,15 +70,28 @@ import java.io.IOException;
  *
  * @author Sebastian Sdorra
  * @since 1.46
- * 
+ *
  * @apiviz.landmark
  */
 public abstract class AdvancedHttpClient
 {
 
+  /**
+   * Creates a {@link ContentTransformer} for the given Content-Type.
+   *
+   * @param type object type
+   * @param contentType content-type
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the content-type
+   *
+   * @return {@link ContentTransformer}
+   */
+  protected abstract ContentTransformer createTransformer(Class type,
+    String contentType);
+
   /**
    * Executes the given request and returns the http response. Implementation
-   * have to check, if the instance if from type 
+   * have to check, if the instance if from type
    * {@link AdvancedHttpRequestWithBody} in order to handle request contents.
    *
    *
@@ -118,8 +131,8 @@ public abstract class AdvancedHttpClient
   }
 
   /**
-   * Returns a request builder with a custom method. Note: not 
-   * every method is supported by the underlying implementation of the http 
+   * Returns a request builder with a custom method. Note: not
+   * every method is supported by the underlying implementation of the http
    * client.
    *
    *
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
index 1acad0efb0..dd5ec6f425 100644
--- a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
@@ -122,6 +122,21 @@ public class AdvancedHttpRequestWithBody
     return new FormContentBuilder(this);
   }
 
+  /**
+   * Transforms the given object to a xml string and set this string as request
+   * content.
+   *
+   * @param object object to transform
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the json content-type
+   *
+   * @return {@code this}
+   */
+  public AdvancedHttpRequestWithBody jsonContent(Object object)
+  {
+    return transformedContent(ContentType.JSON, object);
+  }
+
   /**
    * Sets the raw data as request content.
    *
@@ -182,6 +197,46 @@ public class AdvancedHttpRequestWithBody
     return this;
   }
 
+  /**
+   * Transforms the given object to a string and set this string as request
+   * content. The content-type is used to pick the right
+   * {@link ContentTransformer}. The method will throw an exception if no
+   * {@link ContentTransformer} for the content-type could be found.
+   *
+   * @param contentType content-type
+   * @param object object to transform
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the given content-type
+   *
+   * @return {@code this}
+   */
+  public AdvancedHttpRequestWithBody transformedContent(String contentType,
+    Object object)
+  {
+    ContentTransformer transformer =
+      client.createTransformer(object.getClass(), contentType);
+    ByteSource value = transformer.marshall(object);
+
+    contentType(contentType);
+
+    return rawContent(value);
+  }
+
+  /**
+   * Transforms the given object to a xml string and set this string as request
+   * content.
+   *
+   * @param object object to transform
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the xml content-type
+   *
+   * @return {@code this}
+   */
+  public AdvancedHttpRequestWithBody xmlContent(Object object)
+  {
+    return transformedContent(ContentType.XML, object);
+  }
+
   //~--- get methods ----------------------------------------------------------
 
   /**
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
index 6148b3b355..1cb087710b 100644
--- a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
@@ -34,6 +34,7 @@ package sonia.scm.net.ahc;
 //~--- non-JDK imports --------------------------------------------------------
 
 import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
 import com.google.common.io.ByteSource;
@@ -45,7 +46,7 @@ import java.io.IOException;
 import java.io.InputStream;
 
 /**
- * Http response. The response of a {@link AdvancedHttpRequest} or 
+ * Http response. The response of a {@link AdvancedHttpRequest} or
  * {@link AdvancedHttpRequestWithBody}.
  *
  * @author Sebastian Sdorra
@@ -54,8 +55,56 @@ import java.io.InputStream;
 public abstract class AdvancedHttpResponse
 {
 
+  /**
+   * Returns the response content as byte source.
+   *
+   *
+   * @return response content as byte source
+   * @throws IOException
+   */
+  public abstract ByteSource contentAsByteSource() throws IOException;
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Returns the response headers.
+   *
+   *
+   * @return response headers
+   */
+  public abstract Multimap getHeaders();
+
+  /**
+   * Returns the status code of the response.
+   *
+   *
+   * @return status code
+   */
+  public abstract int getStatus();
+
+  /**
+   * Returns the status text of the response.
+   *
+   *
+   * @return status text
+   */
+  public abstract String getStatusText();
+
   //~--- methods --------------------------------------------------------------
 
+  /**
+   * Creates a {@link ContentTransformer} for the given Content-Type.
+   *
+   * @param type object type
+   * @param contentType content-type
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the content-type
+   *
+   * @return {@link ContentTransformer}
+   */
+  protected abstract ContentTransformer createTransformer(Class type,
+    String contentType);
+
   /**
    * Returns the content of the response as byte array.
    *
@@ -89,6 +138,7 @@ public abstract class AdvancedHttpResponse
   {
     ByteSource content = contentAsByteSource();
     BufferedReader reader = null;
+
     if (content != null)
     {
       reader = content.asCharSource(Charsets.UTF_8).openBufferedStream();
@@ -109,6 +159,7 @@ public abstract class AdvancedHttpResponse
   {
     ByteSource content = contentAsByteSource();
     InputStream stream = null;
+
     if (content != null)
     {
       stream = content.openBufferedStream();
@@ -116,15 +167,6 @@ public abstract class AdvancedHttpResponse
 
     return stream;
   }
-  
-  /**
-   * Returns the response content as byte source.
-   * 
-   * 
-   * @return response content as byte source
-   * @throws IOException 
-   */
-  public abstract ByteSource contentAsByteSource() throws IOException;
 
   /**
    * Returns the response content as string.
@@ -138,6 +180,7 @@ public abstract class AdvancedHttpResponse
   {
     ByteSource content = contentAsByteSource();
     String value = null;
+
     if (content != null)
     {
       value = content.asCharSource(Charsets.UTF_8).read();
@@ -146,6 +189,99 @@ public abstract class AdvancedHttpResponse
     return value;
   }
 
+  /**
+   * Transforms the response content from json to the given type.
+   *
+   * @param  object type
+   * @param type object type
+   *
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the json content-type
+   *
+   * @return transformed object
+   *
+   * @throws IOException
+   */
+  public  T contentFromJson(Class type) throws IOException
+  {
+    return contentTransformed(type, ContentType.JSON);
+  }
+
+  /**
+   * Transforms the response content from xml to the given type.
+   *
+   * @param  object type
+   * @param type object type
+   *
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the xml content-type
+   *
+   * @return transformed object
+   *
+   * @throws IOException
+   */
+  public  T contentFromXml(Class type) throws IOException
+  {
+    return contentTransformed(type, ContentType.XML);
+  }
+
+  /**
+   * Transforms the response content to the given type. The method uses the
+   * content-type header to pick the right {@link ContentTransformer}.
+   *
+   * @param  object type
+   * @param type object type
+   *
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the content-type
+   *
+   * @return transformed object
+   *
+   * @throws IOException
+   */
+  public  T contentTransformed(Class type) throws IOException
+  {
+    String contentType = getFirstHeader("Content-Type");
+
+    if (Strings.isNullOrEmpty(contentType))
+    {
+      throw new ContentTransformerException(
+        "response does not return a Content-Type header");
+    }
+
+    return contentTransformed(type, contentType);
+  }
+
+  /**
+   * Transforms the response content from xml to the given type.
+   *
+   * @param  object type
+   * @param type object type
+   * @param contentType type to pick {@link ContentTransformer}
+   *
+   * @throws ContentTransformerNotFoundException if no
+   *   {@link ContentTransformer} could be found for the content-type
+   *
+   * @return transformed object
+   *
+   * @throws IOException
+   */
+  public  T contentTransformed(Class type, String contentType)
+    throws IOException
+  {
+    T object = null;
+    ByteSource source = contentAsByteSource();
+
+    if (source != null)
+    {
+      ContentTransformer transformer = createTransformer(type, contentType);
+
+      object = transformer.unmarshall(type, contentAsByteSource());
+    }
+
+    return object;
+  }
+
   //~--- get methods ----------------------------------------------------------
 
   /**
@@ -162,38 +298,15 @@ public abstract class AdvancedHttpResponse
   }
 
   /**
-   * Returns the response headers.
-   *
-   *
-   * @return response headers
-   */
-  public abstract Multimap getHeaders();
-
-  /**
-   * Returns {@code true} if the response was successful. A response is 
+   * Returns {@code true} if the response was successful. A response is
    * successful, if the status code is greater than 199 and lower than 400.
-   * 
+   *
    * @return {@code true} if the response was successful
    */
   public boolean isSuccessful()
   {
     int status = getStatus();
-    return status > 199 && status < 400;
-  }
-  
-  /**
-   * Returns the status code of the response.
-   *
-   *
-   * @return status code
-   */
-  public abstract int getStatus();
 
-  /**
-   * Returns the status text of the response.
-   *
-   *
-   * @return status text
-   */
-  public abstract String getStatusText();
+    return (status > 199) && (status < 400);
+  }
 }
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java
new file mode 100644
index 0000000000..0902e2ff79
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformer.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+import com.google.common.io.ByteSource;
+import sonia.scm.plugin.ExtensionPoint;
+
+/**
+ * Transforms {@link ByteSource} content to an object and vice versa. This class
+ * is an extension point, this means that plugins can define their own
+ * {@link ContentTransformer} implementations by implementing the interface and
+ * annotate the implementation with the {@link sonia.scm.plugin.ext.Extension} 
+ * annotation.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@ExtensionPoint
+public interface ContentTransformer
+{
+  
+  /**
+   * Returns {@code true} if the transformer is responsible for the given 
+   * object and content type.
+   * 
+   * @param type object type
+   * @param contentType content type
+   * 
+   * @return {@code true} if the transformer is responsible
+   */
+  public boolean isResponsible(Class type, String contentType);
+
+  /**
+   * Marshalls the given object into a {@link ByteSource}.
+   *
+   *
+   * @param object object to marshall
+   *
+   * @return string content
+   */
+  public ByteSource marshall(Object object);
+
+  /**
+   * Unmarshall the {@link ByteSource} content to an object of the given type.
+   *
+   *
+   * @param type type of result object
+   * @param content content
+   * @param  type of result object
+   *
+   * @return
+   */
+  public  T unmarshall(Class type, ByteSource content);
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java
new file mode 100644
index 0000000000..d4e2299206
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerException.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * A {@link ContentTransformerException} is thrown if an error occurs during
+ * content transformation by an {@link ContentTransformer}.
+ *
+ * @author Sebastian Sdorra
+ *
+ * @since 1.46
+ */
+public class ContentTransformerException extends RuntimeException
+{
+
+  /** Field description */
+  private static final long serialVersionUID = 367333504151208563L;
+
+  //~--- constructors ---------------------------------------------------------
+
+  /**
+   * Constructs a new {@link ContentTransformerException}.
+   *
+   *
+   * @param message exception message
+   */
+  public ContentTransformerException(String message)
+  {
+    super(message);
+  }
+
+  /**
+   * Constructs a new {@link ContentTransformerException}.
+   *
+   *
+   * @param message exception message
+   * @param cause exception cause
+   */
+  public ContentTransformerException(String message, Throwable cause)
+  {
+    super(message, cause);
+  }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java
new file mode 100644
index 0000000000..a8e85cdaf2
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentTransformerNotFoundException.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * The ContentTransformerNotFoundException is thrown, if no
+ * {@link ContentTransformer} could be found for the given type.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class ContentTransformerNotFoundException
+  extends ContentTransformerException
+{
+
+  /** Field description */
+  private static final long serialVersionUID = 6374525163951460938L;
+
+  //~--- constructors ---------------------------------------------------------
+
+  /**
+   * Constructs a new ContentTransformerNotFoundException.
+   *
+   *
+   * @param message exception message
+   */
+  public ContentTransformerNotFoundException(String message)
+  {
+    super(message);
+  }
+}
diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java b/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java
new file mode 100644
index 0000000000..8e3b299f96
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/ContentType.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+/**
+ * Content-Types.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public final class ContentType
+{
+
+  /** json content type */
+  public static final String JSON = "application/json";
+
+  /** xml content type */
+  public static final String XML = "application/xml";
+
+  //~--- constructors ---------------------------------------------------------
+
+  private ContentType() {}
+}
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
index 18b291d8ad..59549c24dd 100644
--- a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
@@ -31,26 +31,22 @@
 
 package sonia.scm.net.ahc;
 
-import java.io.IOException;
 import org.junit.Test;
 import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
 
 /**
  *
  * @author Sebastian Sdorra
  */
+@RunWith(MockitoJUnitRunner.class)
 public class AdvancedHttpClientTest {
 
-  private final AdvancedHttpClient client = new  AdvancedHttpClient()
-  {
-
-    @Override
-    protected AdvancedHttpResponse request(
-      BaseHttpRequest request) throws IOException
-    {
-      throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
-    }
-  };
+  @Mock(answer = Answers.CALLS_REAL_METHODS)
+  private AdvancedHttpClient client;
 
   private static final String URL = "https://www.scm-manager.org";
   
@@ -101,4 +97,12 @@ public class AdvancedHttpClientTest {
     assertEquals(URL, request.getUrl());
     assertEquals(HttpMethod.HEAD, request.getMethod());
   }
+  
+  @Test
+  public void testMethod()
+  {
+    AdvancedHttpRequestWithBody request = client.method("PROPFIND", URL);
+    assertEquals(URL, request.getUrl());
+    assertEquals("PROPFIND", request.getMethod());
+  }
 }
\ No newline at end of file
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
index 8e656133e7..5a7c55a46d 100644
--- a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
@@ -33,11 +33,14 @@ package sonia.scm.net.ahc;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.ByteSource;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import org.junit.Test;
 import static org.junit.Assert.*;
 import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.*;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
@@ -55,6 +58,9 @@ public class AdvancedHttpRequestWithBodyTest {
   @Mock
   private AdvancedHttpClient ahc;
   
+  @Mock
+  private ContentTransformer transformer;
+  
   private AdvancedHttpRequestWithBody request;
   
   @Rule
@@ -118,6 +124,42 @@ public class AdvancedHttpRequestWithBodyTest {
     assertThat(request.getContent(), instanceOf(StringContent.class));
   }
   
+  @Test
+  public void testXmlContent() throws IOException{
+    when(ahc.createTransformer(String.class, ContentType.XML)).thenReturn(transformer);
+    when(transformer.marshall("")).thenReturn(ByteSource.wrap("".getBytes(Charsets.UTF_8)));
+    Content content = request.xmlContent("").getContent();
+    assertThat(content, instanceOf(ByteSourceContent.class));
+    ByteSourceContent bsc = (ByteSourceContent) content;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    bsc.process(baos);
+    assertEquals("", baos.toString("UTF-8"));
+  }
+  
+  @Test
+  public void testJsonContent() throws IOException{
+    when(ahc.createTransformer(String.class, ContentType.JSON)).thenReturn(transformer);
+    when(transformer.marshall("{}")).thenReturn(ByteSource.wrap("{'root': {}}".getBytes(Charsets.UTF_8)));
+    Content content = request.jsonContent("{}").getContent();
+    assertThat(content, instanceOf(ByteSourceContent.class));
+    ByteSourceContent bsc = (ByteSourceContent) content;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    bsc.process(baos);
+    assertEquals("{'root': {}}", baos.toString("UTF-8"));
+  }
+  
+ @Test
+  public void testTransformedContent() throws IOException{
+    when(ahc.createTransformer(String.class, "text/plain")).thenReturn(transformer);
+    when(transformer.marshall("hello")).thenReturn(ByteSource.wrap("hello world".getBytes(Charsets.UTF_8)));
+    Content content = request.transformedContent("text/plain", "hello").getContent();
+    assertThat(content, instanceOf(ByteSourceContent.class));
+    ByteSourceContent bsc = (ByteSourceContent) content;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    bsc.process(baos);
+    assertEquals("hello world", baos.toString("UTF-8"));
+  }
+  
   @Test
   public void testSelf()
   {
diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
index 277bda856d..6fc149f36c 100644
--- a/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
@@ -55,6 +55,9 @@ public class AdvancedHttpResponseTest {
 
   @Mock(answer = Answers.CALLS_REAL_METHODS)
   private AdvancedHttpResponse response;
+  
+  @Mock
+  private ContentTransformer transformer;
 
   @Test
   public void testContent() throws IOException
@@ -65,6 +68,12 @@ public class AdvancedHttpResponseTest {
     assertEquals("test123", new String(data, Charsets.UTF_8));
   }
   
+  @Test
+  public void testContentWithoutByteSource() throws IOException
+  {
+    assertNull(response.content());
+  }
+  
   @Test
   public void testContentAsString() throws IOException
   {
@@ -73,6 +82,12 @@ public class AdvancedHttpResponseTest {
     assertEquals("123test", response.contentAsString());
   }
   
+  @Test
+  public void testContentAsStingWithoutByteSource() throws IOException 
+  {
+    assertNull(response.contentAsString());
+  }
+  
   @Test
   public void testContentAsReader() throws IOException
   {
@@ -81,6 +96,12 @@ public class AdvancedHttpResponseTest {
     assertEquals("abc123", CharStreams.toString(response.contentAsReader()));
   }
   
+  @Test
+  public void testContentAsReaderWithoutByteSource() throws IOException
+  {
+    assertNull(response.contentAsReader());
+  }
+  
   @Test
   public void testContentAsStream() throws IOException
   {
@@ -90,6 +111,69 @@ public class AdvancedHttpResponseTest {
     assertEquals("cde456", new String(data, Charsets.UTF_8));
   }
   
+  @Test
+  public void testContentAsStreamWithoutByteSource() throws IOException
+  {
+    assertNull(response.contentAsStream());
+  }
+  
+  @Test
+  public void testContentFromJson() throws IOException{
+    ByteSource bs = ByteSource.wrap("{}".getBytes(Charsets.UTF_8));
+    when(response.contentAsByteSource()).thenReturn(bs);
+    when(response.createTransformer(String.class, ContentType.JSON)).thenReturn(transformer);
+    when(transformer.unmarshall(String.class, bs)).thenReturn("{root: null}");
+    String c = response.contentFromJson(String.class);
+    assertEquals("{root: null}", c);
+  }
+  
+  @Test
+  public void testContentFromXml() throws IOException{
+    ByteSource bs = ByteSource.wrap("".getBytes(Charsets.UTF_8));
+    when(response.contentAsByteSource()).thenReturn(bs);
+    when(response.createTransformer(String.class, ContentType.XML)).thenReturn(transformer);
+    when(transformer.unmarshall(String.class, bs)).thenReturn("");
+    String c = response.contentFromXml(String.class);
+    assertEquals("", c);
+  }
+  
+  @Test(expected = ContentTransformerException.class)
+  public void testContentTransformedWithoutHeader() throws IOException{
+    Multimap map = LinkedHashMultimap.create();
+    when(response.getHeaders()).thenReturn(map);
+    response.contentTransformed(String.class);
+  }
+  
+  @Test
+  public void testContentTransformedFromHeader() throws IOException{
+    Multimap map = LinkedHashMultimap.create();
+    map.put("Content-Type", "text/plain");
+    when(response.getHeaders()).thenReturn(map);
+    when(response.createTransformer(String.class, "text/plain")).thenReturn(
+      transformer);
+    ByteSource bs = ByteSource.wrap("hello".getBytes(Charsets.UTF_8));
+    when(response.contentAsByteSource()).thenReturn(bs);
+    when(transformer.unmarshall(String.class, bs)).thenReturn("hello world");
+    String v = response.contentTransformed(String.class);
+    assertEquals("hello world", v);
+  }
+  
+  @Test
+  public void testContentTransformed() throws IOException{
+    when(response.createTransformer(String.class, "text/plain")).thenReturn(
+      transformer);
+    ByteSource bs = ByteSource.wrap("hello".getBytes(Charsets.UTF_8));
+    when(response.contentAsByteSource()).thenReturn(bs);
+    when(transformer.unmarshall(String.class, bs)).thenReturn("hello world");
+    String v = response.contentTransformed(String.class, "text/plain");
+    assertEquals("hello world", v);
+  }
+  
+  @Test
+  public void testContentTransformedWithoutByteSource() throws IOException{
+    assertNull(response.contentTransformed(String.class, "text/plain"));
+  }
+  
   @Test
   public void testGetFirstHeader() throws IOException
   {
diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
index 100f171cd1..9087755f93 100644
--- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
+++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java
@@ -157,6 +157,11 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import sonia.scm.net.ahc.AdvancedHttpClient;
+import sonia.scm.net.ahc.ContentTransformer;
+import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
+import sonia.scm.net.ahc.JsonContentTransformer;
+import sonia.scm.net.ahc.XmlContentTransformer;
 import sonia.scm.web.UserAgentParser;
 
 /**
@@ -219,7 +224,7 @@ public class ScmServletModule extends ServletModule
     PATTERN_STYLESHEET, "*.json", "*.xml", "*.txt" };
 
   /** Field description */
-  private static Logger logger =
+  private static final Logger logger =
     LoggerFactory.getLogger(ScmServletModule.class);
 
   //~--- constructors ---------------------------------------------------------
@@ -315,6 +320,13 @@ public class ScmServletModule extends ServletModule
 
     // bind httpclient
     bind(HttpClient.class, URLHttpClient.class);
+    
+    // bind ahc
+    Multibinder transformers =
+      Multibinder.newSetBinder(binder(), ContentTransformer.class);
+    transformers.addBinding().to(XmlContentTransformer.class);
+    transformers.addBinding().to(JsonContentTransformer.class);
+    bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class);
 
     // bind resourcemanager
     if (context.getStage() == Stage.DEVELOPMENT)
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
index 6b80d6bdae..2c19afb44d 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
@@ -65,12 +65,14 @@ import java.net.URL;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 
+import java.util.Set;
+
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 
 /**
- * Default implementation of the {@link AdvancedHttpClient}. The default 
+ * Default implementation of the {@link AdvancedHttpClient}. The default
  * implementation uses {@link HttpURLConnection}.
  *
  * @author Sebastian Sdorra
@@ -79,16 +81,10 @@ import javax.net.ssl.TrustManager;
 public class DefaultAdvancedHttpClient extends AdvancedHttpClient
 {
 
-  /** credential separator */
-  private static final String CREDENTIAL_SEPARATOR = ":";
-
   /** proxy authorization header */
   @VisibleForTesting
   static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
 
-  /** basic authentication prefix */
-  private static final String PREFIX_BASIC_AUTHENTICATION = "Basic ";
-
   /** connection timeout */
   @VisibleForTesting
   static final int TIMEOUT_CONNECTION = 30000;
@@ -97,6 +93,12 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
   @VisibleForTesting
   static final int TIMEOUT_RAED = 1200000;
 
+  /** credential separator */
+  private static final String CREDENTIAL_SEPARATOR = ":";
+
+  /** basic authentication prefix */
+  private static final String PREFIX_BASIC_AUTHENTICATION = "Basic ";
+
   /**
    * the logger for DefaultAdvancedHttpClient
    */
@@ -110,17 +112,20 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
    *
    *
    * @param configuration scm-manager main configuration
+   * @param contentTransformers content transformer
    */
   @Inject
-  public DefaultAdvancedHttpClient(ScmConfiguration configuration)
+  public DefaultAdvancedHttpClient(ScmConfiguration configuration,
+    Set contentTransformers)
   {
     this.configuration = configuration;
+    this.contentTransformers = contentTransformers;
   }
 
   //~--- methods --------------------------------------------------------------
 
   /**
-   * Creates a new {@link HttpURLConnection} from the given {@link URL}. The 
+   * Creates a new {@link HttpURLConnection} from the given {@link URL}. The
    * method is visible for testing.
    *
    *
@@ -157,6 +162,34 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
       address));
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected ContentTransformer createTransformer(Class type, String contentType)
+  {
+    ContentTransformer responsible = null;
+
+    for (ContentTransformer transformer : contentTransformers)
+    {
+      if (transformer.isResponsible(type, contentType))
+      {
+        responsible = transformer;
+
+        break;
+      }
+    }
+
+    if (responsible == null)
+    {
+      throw new ContentTransformerNotFoundException(
+        "could not find content transformer for content type ".concat(
+          contentType));
+    }
+
+    return responsible;
+  }
+
   /**
    * Executes the given request and returns the server response.
    *
@@ -210,7 +243,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
       applyContent(connection, content);
     }
 
-    return new DefaultAdvancedHttpResponse(connection,
+    return new DefaultAdvancedHttpResponse(this, connection,
       connection.getResponseCode(), connection.getResponseMessage());
   }
 
@@ -359,4 +392,7 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
 
   /** scm-manager main configuration */
   private final ScmConfiguration configuration;
+
+  /** set of content transformers */
+  private final Set contentTransformers;
 }
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
index c42e36deb5..21179ce1b6 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
@@ -62,14 +62,15 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse
   /**
    * Constructs a new {@link DefaultAdvancedHttpResponse}.
    *
-   *
+   * @param client ahc
    * @param connection http connection
    * @param status response status code
    * @param statusText response status text
    */
-  DefaultAdvancedHttpResponse(HttpURLConnection connection, int status,
-    String statusText)
+  DefaultAdvancedHttpResponse(DefaultAdvancedHttpClient client,
+    HttpURLConnection connection, int status, String statusText)
   {
+    this.client = client;
     this.connection = connection;
     this.status = status;
     this.statusText = statusText;
@@ -126,6 +127,18 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse
     return statusText;
   }
 
+  //~--- methods --------------------------------------------------------------
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected ContentTransformer createTransformer(Class type,
+    String contentType)
+  {
+    return client.createTransformer(type, contentType);
+  }
+
   //~--- inner classes --------------------------------------------------------
 
   /**
@@ -196,6 +209,9 @@ public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse
 
   //~--- fields ---------------------------------------------------------------
 
+  /** Field description */
+  private final DefaultAdvancedHttpClient client;
+
   /** http connection */
   private final HttpURLConnection connection;
 
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java
new file mode 100644
index 0000000000..29d45f00c5
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/JsonContentTransformer.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.io.ByteSource;
+
+import org.codehaus.jackson.map.ObjectMapper;
+
+import sonia.scm.plugin.ext.Extension;
+import sonia.scm.util.IOUtil;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.ws.rs.core.MediaType;
+import org.codehaus.jackson.map.AnnotationIntrospector;
+import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
+import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
+
+/**
+ * {@link ContentTransformer} for json. The {@link JsonContentTransformer} uses
+ * jacksons {@link ObjectMapper} to marshalling/unmarshalling.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@Extension
+public class JsonContentTransformer implements ContentTransformer
+{
+
+  public JsonContentTransformer()
+  {
+    this.mapper = new ObjectMapper();
+    AnnotationIntrospector jackson = new JacksonAnnotationIntrospector();
+    AnnotationIntrospector jaxb = new JaxbAnnotationIntrospector();
+    AnnotationIntrospector pair = new AnnotationIntrospector.Pair(jackson, jaxb);
+    this.mapper.setAnnotationIntrospector(pair);
+  }
+
+  
+  
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ByteSource marshall(Object object)
+  {
+    ByteSource source = null;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+    try
+    {
+      mapper.writeValue(baos, object);
+      source = ByteSource.wrap(baos.toByteArray());
+    }
+    catch (IOException ex)
+    {
+      throw new ContentTransformerException("could not marshall object", ex);
+    }
+
+    return source;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public  T unmarshall(Class type, ByteSource content)
+  {
+    T object = null;
+    InputStream stream = null;
+
+    try
+    {
+      stream = content.openBufferedStream();
+      object = mapper.readValue(stream, type);
+    }
+    catch (IOException ex)
+    {
+      throw new ContentTransformerException("could not unmarshall content", ex);
+    }
+    finally
+    {
+      IOUtil.close(stream);
+    }
+
+    return object;
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Returns {@code true}, if the content type is compatible with
+   * application/json.
+   *
+   *
+   * @param type object type
+   * @param contentType content type
+   *
+   * @return {@code true}, if the content type is compatible with
+   *   application/json
+   */
+  @Override
+  public boolean isResponsible(Class type, String contentType)
+  {
+    return MediaType.valueOf(contentType).isCompatible(
+      MediaType.APPLICATION_JSON_TYPE);
+  }
+
+  //~--- fields ---------------------------------------------------------------
+
+  /** object mapper */
+  private final ObjectMapper mapper;
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
new file mode 100644
index 0000000000..1fd1983d23
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/XmlContentTransformer.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 2. Redistributions in
+ * binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other
+ * materials provided with the distribution. 3. Neither the name of SCM-Manager;
+ * nor the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.net.ahc;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import com.google.common.io.ByteSource;
+
+import sonia.scm.plugin.ext.Extension;
+import sonia.scm.util.IOUtil;
+
+//~--- JDK imports ------------------------------------------------------------
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.ws.rs.core.MediaType;
+
+import javax.xml.bind.DataBindingException;
+import javax.xml.bind.JAXB;
+
+/**
+ * {@link ContentTransformer} for xml. The {@link XmlContentTransformer} uses 
+ * jaxb to marshalling/unmarshalling.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+@Extension
+public class XmlContentTransformer implements ContentTransformer
+{
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ByteSource marshall(Object object)
+  {
+    ByteSource source = null;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+    try
+    {
+      JAXB.marshal(object, baos);
+      source = ByteSource.wrap(baos.toByteArray());
+    }
+    catch (DataBindingException ex)
+    {
+      throw new ContentTransformerException("could not marshall object", ex);
+    }
+
+    return source;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public  T unmarshall(Class type, ByteSource content)
+  {
+    T object = null;
+    InputStream stream = null;
+
+    try
+    {
+      stream = content.openBufferedStream();
+      object = JAXB.unmarshal(stream, type);
+    }
+    catch (IOException ex)
+    {
+      throw new ContentTransformerException("could not unmarshall content", ex);
+    }
+    catch (DataBindingException ex)
+    {
+      throw new ContentTransformerException("could not unmarshall content", ex);
+    }
+    finally
+    {
+      IOUtil.close(stream);
+    }
+
+    return object;
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Returns {@code true}, if the content type is compatible with 
+   * application/xml.
+   *
+   *
+   * @param type object type
+   * @param contentType content type
+   *
+   * @return {@code true}, if the content type is compatible with 
+   *   application/xml
+   */
+  @Override
+  public boolean isResponsible(Class type, String contentType)
+  {
+    return MediaType.valueOf(contentType).isCompatible(
+      MediaType.APPLICATION_XML_TYPE);
+  }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
index 451c07b5cc..cd26af9d4b 100644
--- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
@@ -59,6 +59,9 @@ import java.net.HttpURLConnection;
 import java.net.SocketAddress;
 import java.net.URL;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSocketFactory;
 
@@ -87,6 +90,20 @@ public class DefaultAdvancedHttpClientTest
       DefaultAdvancedHttpClient.TIMEOUT_CONNECTION);
     verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
   }
+  
+  @Test(expected = ContentTransformerNotFoundException.class)
+  public void testContentTransformerNotFound(){
+    client.createTransformer(String.class, "text/plain");
+  }
+  
+  @Test
+  public void testContentTransformer(){
+    ContentTransformer transformer = mock(ContentTransformer.class);
+    when(transformer.isResponsible(String.class, "text/plain")).thenReturn(Boolean.TRUE);
+    transformers.add(transformer);
+    ContentTransformer t = client.createTransformer(String.class, "text/plain");
+    assertSame(transformer, t);
+  }
 
   /**
    * Method description
@@ -266,7 +283,8 @@ public class DefaultAdvancedHttpClientTest
   public void setUp()
   {
     configuration = new ScmConfiguration();
-    client = new TestingAdvacedHttpClient(configuration);
+    transformers = new HashSet();
+    client = new TestingAdvacedHttpClient(configuration, transformers);
   }
 
   //~--- inner classes --------------------------------------------------------
@@ -276,7 +294,7 @@ public class DefaultAdvancedHttpClientTest
    *
    *
    * @version        Enter version here..., 15/05/01
-   * @author         Enter your name here...    
+   * @author         Enter your name here...
    */
   public class TestingAdvacedHttpClient extends DefaultAdvancedHttpClient
   {
@@ -286,10 +304,12 @@ public class DefaultAdvancedHttpClientTest
      *
      *
      * @param configuration
+     * @param transformers
      */
-    public TestingAdvacedHttpClient(ScmConfiguration configuration)
+    public TestingAdvacedHttpClient(ScmConfiguration configuration,
+      Set transformers)
     {
-      super(configuration);
+      super(configuration, transformers);
     }
 
     //~--- methods ------------------------------------------------------------
@@ -349,4 +369,7 @@ public class DefaultAdvancedHttpClientTest
   /** Field description */
   @Mock
   private HttpsURLConnection connection;
+
+  /** Field description */
+  private Set transformers;
 }
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
index 063b8b6f09..f19c89eb5f 100644
--- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
@@ -47,6 +47,8 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import sonia.scm.config.ScmConfiguration;
+
 import static org.hamcrest.Matchers.*;
 
 import static org.junit.Assert.*;
@@ -60,6 +62,7 @@ import java.io.IOException;
 
 import java.net.HttpURLConnection;
 
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 
@@ -85,8 +88,8 @@ public class DefaultAdvancedHttpResponseTest
 
     when(connection.getInputStream()).thenReturn(bais);
 
-    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection,
-                                      200, "OK");
+    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+                                      connection, 200, "OK");
     ByteSource content = response.contentAsByteSource();
 
     assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
@@ -107,8 +110,8 @@ public class DefaultAdvancedHttpResponseTest
     when(connection.getInputStream()).thenThrow(IOException.class);
     when(connection.getErrorStream()).thenReturn(bais);
 
-    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection,
-                                      404, "NOT FOUND");
+    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+                                      connection, 404, "NOT FOUND");
     ByteSource content = response.contentAsByteSource();
 
     assertEquals("test", content.asCharSource(Charsets.UTF_8).read());
@@ -127,8 +130,8 @@ public class DefaultAdvancedHttpResponseTest
     map.put("Test", test);
     when(connection.getHeaderFields()).thenReturn(map);
 
-    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(connection,
-                                      200, "OK");
+    AdvancedHttpResponse response = new DefaultAdvancedHttpResponse(client,
+                                      connection, 200, "OK");
     Multimap headers = response.getHeaders();
 
     assertThat(headers.get("Test"), contains("One", "Two"));
@@ -137,6 +140,11 @@ public class DefaultAdvancedHttpResponseTest
 
   //~--- fields ---------------------------------------------------------------
 
+  /** Field description */
+  private final DefaultAdvancedHttpClient client =
+    new DefaultAdvancedHttpClient(new ScmConfiguration(),
+      new HashSet());
+
   /** Field description */
   @Mock
   private HttpURLConnection connection;
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
new file mode 100644
index 0000000000..ff50427470
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * http://bitbucket.org/sdorra/scm-manager
+ * 
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.io.ByteSource;
+import java.io.IOException;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class JsonContentTransformerTest {
+
+
+  private final JsonContentTransformer transformer = new JsonContentTransformer();
+
+  @Test
+  public void testIsResponsible()
+  {
+    assertTrue(transformer.isResponsible(String.class, "application/json"));
+    assertTrue(transformer.isResponsible(String.class, "application/json;charset=UTF-8"));
+    assertFalse(transformer.isResponsible(String.class, "text/plain"));
+  }
+  
+  @Test
+  public void testMarshallAndUnmarshall() throws IOException{
+    ByteSource bs = transformer.marshall(new TestObject("test"));
+    TestObject to = transformer.unmarshall(TestObject.class, bs);
+    assertEquals("test", to.value);
+  }
+  
+  @Test(expected = ContentTransformerException.class)
+  public void testUnmarshallIOException() throws IOException{
+    ByteSource bs = mock(ByteSource.class);
+    when(bs.openBufferedStream()).thenThrow(IOException.class);
+    transformer.unmarshall(String.class, bs);
+  }
+  
+  private static class TestObject2 {}
+  
+  @XmlRootElement(name = "test")
+  @XmlAccessorType(XmlAccessType.FIELD)
+  private static class TestObject {
+  
+    private String value;
+
+    public TestObject()
+    {
+    }
+    
+    public TestObject(String value)
+    {
+      this.value = value;
+    }
+    
+  }
+}
\ No newline at end of file
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java
new file mode 100644
index 0000000000..ce87752b4f
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/XmlContentTransformerTest.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2014, Sebastian Sdorra
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * http://bitbucket.org/sdorra/scm-manager
+ * 
+ */
+
+package sonia.scm.net.ahc;
+
+import com.google.common.io.ByteSource;
+import java.io.IOException;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+public class XmlContentTransformerTest {
+
+  private final XmlContentTransformer transformer = new XmlContentTransformer();
+
+  @Test
+  public void testIsResponsible()
+  {
+    assertTrue(transformer.isResponsible(String.class, "application/xml"));
+    assertTrue(transformer.isResponsible(String.class, "application/xml;charset=UTF-8"));
+    assertFalse(transformer.isResponsible(String.class, "text/plain"));
+  }
+  
+  @Test
+  public void testMarshallAndUnmarshall() throws IOException{
+    ByteSource bs = transformer.marshall(new TestObject("test"));
+    TestObject to = transformer.unmarshall(TestObject.class, bs);
+    assertEquals("test", to.value);
+  }
+  
+  @Test(expected = ContentTransformerException.class)
+  public void testUnmarshallIOException() throws IOException{
+    ByteSource bs = mock(ByteSource.class);
+    when(bs.openBufferedStream()).thenThrow(IOException.class);
+    transformer.unmarshall(String.class, bs);
+  }
+  
+  private static class TestObject2 {}
+  
+  @XmlRootElement(name = "test")
+  @XmlAccessorType(XmlAccessType.FIELD)
+  private static class TestObject {
+  
+    private String value;
+
+    public TestObject()
+    {
+    }
+    
+    public TestObject(String value)
+    {
+      this.value = value;
+    }
+    
+  }
+
+}
\ No newline at end of file

From a7d33529fd93d9e3a7671927d4c2e878cac4fb4a Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra 
Date: Sun, 3 May 2015 17:06:35 +0200
Subject: [PATCH 5/7] mark old http-client api as deprecated

---
 scm-core/src/main/java/sonia/scm/net/HttpClient.java          | 3 +++
 scm-core/src/main/java/sonia/scm/net/HttpRequest.java         | 4 ++++
 scm-core/src/main/java/sonia/scm/net/HttpResponse.java        | 4 ++++
 .../java/sonia/scm/net/ahc/JsonContentTransformerTest.java    | 1 -
 4 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/scm-core/src/main/java/sonia/scm/net/HttpClient.java b/scm-core/src/main/java/sonia/scm/net/HttpClient.java
index b9b891f557..6af89bcb71 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpClient.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpClient.java
@@ -48,7 +48,10 @@ import java.util.Map;
  * @apiviz.landmark
  * @apiviz.uses sonia.scm.net.HttpRequest
  * @apiviz.uses sonia.scm.net.HttpResponse
+ * 
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpClient} instead.
  */
+@Deprecated
 public interface HttpClient
 {
 
diff --git a/scm-core/src/main/java/sonia/scm/net/HttpRequest.java b/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
index f3ccf9719c..3f171a3fa7 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpRequest.java
@@ -51,7 +51,11 @@ import java.util.Map;
  *
  * @author Sebastian Sdorra
  * @since 1.9
+ * 
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpRequest} or 
+ *   {@link sonia.scm.net.ahc.AdvancedHttpRequestWithBody} instead.
  */
+@Deprecated
 public class HttpRequest
 {
 
diff --git a/scm-core/src/main/java/sonia/scm/net/HttpResponse.java b/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
index 83cdf9ee61..ef297c2809 100644
--- a/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
+++ b/scm-core/src/main/java/sonia/scm/net/HttpResponse.java
@@ -41,12 +41,16 @@ import java.io.InputStream;
 
 import java.util.List;
 import java.util.Map;
+import sonia.scm.net.ahc.AdvancedHttpResponse;
 
 /**
  * Response of a {@link HttpRequest} execute by the {@link HttpClient}.
  *
  * @author Sebastian Sdorra
+ * 
+ * @deprecated use {@link AdvancedHttpResponse} instead
  */
+@Deprecated
 public interface HttpResponse extends Closeable
 {
 
diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
index ff50427470..1828c5ccf2 100644
--- a/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.java
@@ -38,7 +38,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.junit.Test;
 import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
 import static org.mockito.Mockito.*;
 
 /**

From 656085c698afdd64826670d5d0cd25392b75f7ed Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra 
Date: Sun, 17 May 2015 14:04:58 +0200
Subject: [PATCH 6/7] use ahc for internal http operations

---
 .../api/rest/resources/HgConfigResource.java  | 12 ++---
 .../scm/installer/AbstractHgInstaller.java    |  4 +-
 .../java/sonia/scm/installer/HgInstaller.java |  6 +--
 .../scm/installer/HgPackageInstaller.java     | 12 ++---
 .../sonia/scm/installer/HgPackageReader.java  | 34 +++++---------
 .../sonia/scm/repository/HgHookManager.java   | 36 ++++++---------
 .../java/sonia/scm/net/URLHttpClient.java     |  2 +
 .../scm/plugin/DefaultPluginManager.java      | 46 +++----------------
 8 files changed, 52 insertions(+), 100 deletions(-)

diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java
index 7435024e19..3ef39c163f 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java
@@ -43,7 +43,7 @@ import sonia.scm.installer.HgInstallerFactory;
 import sonia.scm.installer.HgPackage;
 import sonia.scm.installer.HgPackageReader;
 import sonia.scm.installer.HgPackages;
-import sonia.scm.net.HttpClient;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 import sonia.scm.repository.HgConfig;
 import sonia.scm.repository.HgRepositoryHandler;
 
@@ -89,8 +89,8 @@ public class HgConfigResource
    * @param pkgReader
    */
   @Inject
-  public HgConfigResource(HttpClient client, HgRepositoryHandler handler,
-                          HgPackageReader pkgReader)
+  public HgConfigResource(AdvancedHttpClient client,
+    HgRepositoryHandler handler, HgPackageReader pkgReader)
   {
     this.client = client;
     this.handler = handler;
@@ -158,7 +158,7 @@ public class HgConfigResource
     if (pkg != null)
     {
       if (HgInstallerFactory.createInstaller().installPackage(client, handler,
-              SCMContext.getContext().getBaseDirectory(), pkg))
+        SCMContext.getContext().getBaseDirectory(), pkg))
       {
         response = Response.noContent().build();
       }
@@ -262,7 +262,7 @@ public class HgConfigResource
   @POST
   @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
   public Response setConfig(@Context UriInfo uriInfo, HgConfig config)
-          throws IOException
+    throws IOException
   {
     handler.setConfig(config);
     handler.storeConfig();
@@ -338,7 +338,7 @@ public class HgConfigResource
   //~--- fields ---------------------------------------------------------------
 
   /** Field description */
-  private HttpClient client;
+  private AdvancedHttpClient client;
 
   /** Field description */
   private HgRepositoryHandler handler;
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java
index 637825a407..bd5dfd7095 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/AbstractHgInstaller.java
@@ -35,7 +35,6 @@ package sonia.scm.installer;
 
 //~--- non-JDK imports --------------------------------------------------------
 
-import sonia.scm.net.HttpClient;
 import sonia.scm.repository.HgConfig;
 import sonia.scm.repository.HgRepositoryHandler;
 import sonia.scm.util.IOUtil;
@@ -44,6 +43,7 @@ import sonia.scm.util.IOUtil;
 
 import java.io.File;
 import java.io.IOException;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 
 /**
  *
@@ -93,7 +93,7 @@ public abstract class AbstractHgInstaller implements HgInstaller
    * @return
    */
   @Override
-  public boolean installPackage(HttpClient client, HgRepositoryHandler handler,
+  public boolean installPackage(AdvancedHttpClient client, HgRepositoryHandler handler,
                                 File baseDirectory, HgPackage pkg)
   {
     return new HgPackageInstaller(client, handler, baseDirectory,
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java
index 219ce5204e..52f18c1824 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgInstaller.java
@@ -35,7 +35,7 @@ package sonia.scm.installer;
 
 //~--- non-JDK imports --------------------------------------------------------
 
-import sonia.scm.net.HttpClient;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 import sonia.scm.repository.HgConfig;
 import sonia.scm.repository.HgRepositoryHandler;
 
@@ -78,8 +78,8 @@ public interface HgInstaller
    *
    * @return
    */
-  public boolean installPackage(HttpClient client, HgRepositoryHandler handler,
-                                File baseDirectory, HgPackage pkg);
+  public boolean installPackage(AdvancedHttpClient client,
+    HgRepositoryHandler handler, File baseDirectory, HgPackage pkg);
 
   /**
    * Method description
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java
index 91bb6fce0c..7406505b5a 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageInstaller.java
@@ -40,10 +40,10 @@ import org.slf4j.LoggerFactory;
 
 import sonia.scm.SCMContext;
 import sonia.scm.io.ZipUnArchiver;
-import sonia.scm.net.HttpClient;
-import sonia.scm.repository.HgWindowsPackageFix;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 import sonia.scm.repository.HgConfig;
 import sonia.scm.repository.HgRepositoryHandler;
+import sonia.scm.repository.HgWindowsPackageFix;
 import sonia.scm.util.IOUtil;
 
 //~--- JDK imports ------------------------------------------------------------
@@ -80,8 +80,8 @@ public class HgPackageInstaller implements Runnable
    * @param baseDirectory
    * @param pkg
    */
-  public HgPackageInstaller(HttpClient client, HgRepositoryHandler handler,
-    File baseDirectory, HgPackage pkg)
+  public HgPackageInstaller(AdvancedHttpClient client,
+    HgRepositoryHandler handler, File baseDirectory, HgPackage pkg)
   {
     this.client = client;
     this.handler = handler;
@@ -155,7 +155,7 @@ public class HgPackageInstaller implements Runnable
       }
 
       // TODO error handling
-      input = client.get(pkg.getUrl()).getContent();
+      input = client.get(pkg.getUrl()).request().contentAsStream();
       output = new FileOutputStream(file);
       IOUtil.copy(input, output);
     }
@@ -265,7 +265,7 @@ public class HgPackageInstaller implements Runnable
   private File baseDirectory;
 
   /** Field description */
-  private HttpClient client;
+  private AdvancedHttpClient client;
 
   /** Field description */
   private HgRepositoryHandler handler;
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java
index b4ee9cc2c9..a90adff3cc 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/installer/HgPackageReader.java
@@ -36,7 +36,6 @@ package sonia.scm.installer;
 //~--- non-JDK imports --------------------------------------------------------
 
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -44,22 +43,17 @@ import org.slf4j.LoggerFactory;
 import sonia.scm.PlatformType;
 import sonia.scm.cache.Cache;
 import sonia.scm.cache.CacheManager;
-import sonia.scm.net.HttpClient;
-import sonia.scm.net.HttpResponse;
-import sonia.scm.util.IOUtil;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 import sonia.scm.util.SystemUtil;
 import sonia.scm.util.Util;
 
 //~--- JDK imports ------------------------------------------------------------
 
 import java.io.IOException;
-import java.io.InputStream;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.xml.bind.JAXB;
-
 /**
  *
  * @author Sebastian Sdorra
@@ -85,14 +79,14 @@ public class HgPackageReader
    *
    *
    * @param cacheManager
-   * @param httpClientProvider
+   * @param httpClient
    */
   @Inject
   public HgPackageReader(CacheManager cacheManager,
-                         Provider httpClientProvider)
+    AdvancedHttpClient httpClient)
   {
     cache = cacheManager.getCache(String.class, HgPackages.class, CACHENAME);
-    this.httpClientProvider = httpClientProvider;
+    this.httpClient = httpClient;
   }
 
   //~--- get methods ----------------------------------------------------------
@@ -167,7 +161,7 @@ public class HgPackageReader
           if (logger.isDebugEnabled())
           {
             logger.debug("reject package {}, because of wrong platform {}",
-                         pkg.getId(), pkg.getPlatform());
+              pkg.getId(), pkg.getPlatform());
           }
 
           add = false;
@@ -181,7 +175,7 @@ public class HgPackageReader
           if (logger.isDebugEnabled())
           {
             logger.debug("reject package {}, because of wrong arch {}",
-                         pkg.getId(), pkg.getArch());
+              pkg.getId(), pkg.getArch());
           }
 
           add = false;
@@ -218,23 +212,19 @@ public class HgPackageReader
     }
 
     HgPackages packages = null;
-    InputStream input = null;
 
     try
     {
-      HttpResponse response = httpClientProvider.get().get(PACKAGEURL);
-
-      input = response.getContent();
-      packages = JAXB.unmarshal(input, HgPackages.class);
+      //J-
+      packages = httpClient.get(PACKAGEURL)
+                           .request()
+                           .contentFromXml(HgPackages.class);
+      //J+
     }
     catch (IOException ex)
     {
       logger.error("could not read HgPackages from ".concat(PACKAGEURL), ex);
     }
-    finally
-    {
-      IOUtil.close(input);
-    }
 
     if (packages == null)
     {
@@ -251,5 +241,5 @@ public class HgPackageReader
   private Cache cache;
 
   /** Field description */
-  private Provider httpClientProvider;
+  private AdvancedHttpClient httpClient;
 }
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
index 6107e33d05..03c07fd011 100644
--- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgHookManager.java
@@ -47,9 +47,6 @@ import org.slf4j.LoggerFactory;
 
 import sonia.scm.ConfigChangedListener;
 import sonia.scm.config.ScmConfiguration;
-import sonia.scm.net.HttpClient;
-import sonia.scm.net.HttpRequest;
-import sonia.scm.net.HttpResponse;
 import sonia.scm.util.HttpUtil;
 import sonia.scm.util.Util;
 
@@ -60,6 +57,7 @@ import java.io.IOException;
 import java.util.UUID;
 
 import javax.servlet.http.HttpServletRequest;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 
 /**
  *
@@ -86,17 +84,17 @@ public class HgHookManager implements ConfigChangedListener
    *
    * @param configuration
    * @param httpServletRequestProvider
-   * @param httpClientProvider
+   * @param httpClient
    */
   @Inject
   public HgHookManager(ScmConfiguration configuration,
     Provider httpServletRequestProvider,
-    Provider httpClientProvider)
+    AdvancedHttpClient httpClient)
   {
     this.configuration = configuration;
     this.configuration.addListener(this);
     this.httpServletRequestProvider = httpServletRequestProvider;
-    this.httpClientProvider = httpClientProvider;
+    this.httpClient = httpClient;
   }
 
   //~--- methods --------------------------------------------------------------
@@ -359,20 +357,16 @@ public class HgHookManager implements ConfigChangedListener
     {
       url = url.concat("?ping=true");
 
-      if (logger.isTraceEnabled())
-      {
-        logger.trace("check hook url {}", url);
-      }
-
-      HttpRequest request = new HttpRequest(url);
-
-      request.setDisableCertificateValidation(true);
-      request.setDisableHostnameValidation(true);
-      request.setIgnoreProxySettings(true);
-
-      HttpResponse response = httpClientProvider.get().get(request);
-
-      result = response.getStatusCode() == 204;
+      logger.trace("check hook url {}", url);
+      //J-
+      int sc = httpClient.get(url)
+                         .disableHostnameValidation(true)
+                         .disableCertificateValidation(true)
+                         .ignoreProxySettings(true)
+                         .request()
+                         .getStatus();
+      //J+
+      result = sc == 204;
     }
     catch (IOException ex)
     {
@@ -397,7 +391,7 @@ public class HgHookManager implements ConfigChangedListener
   private volatile String hookUrl;
 
   /** Field description */
-  private Provider httpClientProvider;
+  private AdvancedHttpClient httpClient;
 
   /** Field description */
   private Provider httpServletRequestProvider;
diff --git a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
index 5be3496784..83b0fd23d5 100644
--- a/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
+++ b/scm-webapp/src/main/java/sonia/scm/net/URLHttpClient.java
@@ -74,7 +74,9 @@ import sonia.scm.util.HttpUtil;
 /**
  *
  * @author Sebastian Sdorra
+ * @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpClient}
  */
+@Deprecated
 public class URLHttpClient implements HttpClient
 {
 
diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
index b2250a3c21..7ef8f69c4b 100644
--- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
+++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java
@@ -40,21 +40,18 @@ import com.google.common.collect.ImmutableSet.Builder;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import sonia.scm.ConfigChangedListener;
-import sonia.scm.ConfigurationException;
 import sonia.scm.SCMContext;
 import sonia.scm.SCMContextProvider;
 import sonia.scm.cache.Cache;
 import sonia.scm.cache.CacheManager;
 import sonia.scm.config.ScmConfiguration;
 import sonia.scm.io.ZipUnArchiver;
-import sonia.scm.net.HttpClient;
 import sonia.scm.util.AssertUtil;
 import sonia.scm.util.IOUtil;
 import sonia.scm.util.SecurityUtil;
@@ -79,9 +76,7 @@ import java.util.Map;
 import java.util.Set;
 
 import javax.xml.bind.JAXB;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
+import sonia.scm.net.ahc.AdvancedHttpClient;
 
 /**
  *
@@ -122,18 +117,18 @@ public class DefaultPluginManager
    * @param configuration
    * @param pluginLoader
    * @param cacheManager
-   * @param clientProvider
+   * @param httpClient
    */
   @Inject
   public DefaultPluginManager(SCMContextProvider context,
     ScmConfiguration configuration, PluginLoader pluginLoader,
-    CacheManager cacheManager, Provider clientProvider)
+    CacheManager cacheManager, AdvancedHttpClient httpClient)
   {
     this.context = context;
     this.configuration = configuration;
     this.cache = cacheManager.getCache(String.class, PluginCenter.class,
       CACHE_NAME);
-    this.clientProvider = clientProvider;
+    this.httpClient = httpClient;
     installedPlugins = new HashMap();
 
     for (Plugin plugin : pluginLoader.getInstalledPlugins())
@@ -146,16 +141,6 @@ public class DefaultPluginManager
       }
     }
 
-    try
-    {
-      unmarshaller =
-        JAXBContext.newInstance(PluginCenter.class).createUnmarshaller();
-    }
-    catch (JAXBException ex)
-    {
-      throw new ConfigurationException(ex);
-    }
-
     File file = findAdvancedConfiguration();
 
     if (file.exists())
@@ -654,21 +639,9 @@ public class DefaultPluginManager
 
         if (Util.isNotEmpty(pluginUrl))
         {
-          InputStream input = null;
-
           try
           {
-            input = clientProvider.get().get(pluginUrl).getContent();
-
-            /*
-             *  TODO: add gzip support
-             *
-             * if (gzip)
-             * {
-             * input = new GZIPInputStream(input);
-             * }
-             */
-            center = (PluginCenter) unmarshaller.unmarshal(input);
+            center = httpClient.get(pluginUrl).request().contentFromXml(PluginCenter.class);
             preparePlugins(center);
             cache.put(PluginCenter.class.getName(), center);
 
@@ -690,10 +663,6 @@ public class DefaultPluginManager
           {
             logger.error("could not load plugins from plugin center", ex);
           }
-          finally
-          {
-            IOUtil.close(input);
-          }
         }
 
         if (center == null)
@@ -794,7 +763,7 @@ public class DefaultPluginManager
   private Cache cache;
 
   /** Field description */
-  private Provider clientProvider;
+  private AdvancedHttpClient httpClient;
 
   /** Field description */
   private ScmConfiguration configuration;
@@ -807,7 +776,4 @@ public class DefaultPluginManager
 
   /** Field description */
   private AetherPluginHandler pluginHandler;
-
-  /** Field description */
-  private Unmarshaller unmarshaller;
 }

From 96b946a55cb5a2b288df34b1967b6540fa635904 Mon Sep 17 00:00:00 2001
From: Sebastian Sdorra 
Date: Sun, 17 May 2015 14:05:55 +0200
Subject: [PATCH 7/7] close branch issue-709