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-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..aa660f21ca
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpClient.java
@@ -0,0 +1,202 @@
+/**
+ * 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
+{
+
+ /**
+ * 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
+ * {@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..dd5ec6f425
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java
@@ -0,0 +1,271 @@
+/**
+ * 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);
+ }
+
+ /**
+ * 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.
+ *
+ *
+ * @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;
+ }
+
+ /**
+ * 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 ----------------------------------------------------------
+
+ /**
+ * 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..1cb087710b
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java
@@ -0,0 +1,312 @@
+/**
+ * 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.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
+{
+
+ /**
+ * 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.
+ *
+ *
+ * @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 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;
+ }
+
+ /**
+ * 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 ----------------------------------------------------------
+
+ /**
+ * 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 {@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);
+ }
+}
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..c80ae9b1c4
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java
@@ -0,0 +1,409 @@
+/**
+ * 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.LinkedHashMultimap;
+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 extends Object> 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 extends Object> 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 = LinkedHashMultimap.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/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/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 extends Object> 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..59549c24dd
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpClientTest.java
@@ -0,0 +1,108 @@
+/**
+ * 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.Answers;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+/**
+ *
+ * @author Sebastian Sdorra
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AdvancedHttpClientTest {
+
+ @Mock(answer = Answers.CALLS_REAL_METHODS)
+ private AdvancedHttpClient client;
+
+ 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());
+ }
+
+ @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/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..5a7c55a46d
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBodyTest.java
@@ -0,0 +1,169 @@
+/**
+ * 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.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;
+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;
+
+ @Mock
+ private ContentTransformer transformer;
+
+ 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 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()
+ {
+ 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..6fc149f36c
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/net/ahc/AdvancedHttpResponseTest.java
@@ -0,0 +1,214 @@
+/**
+ * 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.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;
+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;
+
+ @Mock
+ private ContentTransformer transformer;
+
+ @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 testContentWithoutByteSource() throws IOException
+ {
+ assertNull(response.content());
+ }
+
+ @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 testContentAsStingWithoutByteSource() throws IOException
+ {
+ assertNull(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 testContentAsReaderWithoutByteSource() throws IOException
+ {
+ assertNull(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));
+ }
+
+ @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
+ {
+ 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
new file mode 100644
index 0000000000..9b3ce995f5
--- /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 extends Object> i1 = Lists.newArrayList("b");
+ Iterable extends Object> 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, contains("b", "c", "d"));
+ }
+
+ @Test
+ public void testRequest() throws IOException{
+ request.request();
+ verify(ahc).request(request);
+ }
+
+ @Test
+ public void testBuilderMethods(){
+ Iterable extends Object> 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 extends Object> 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-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/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/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/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
new file mode 100644
index 0000000000..2c19afb44d
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java
@@ -0,0 +1,398 @@
+/**
+ * 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;
+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 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
+ * implementation uses {@link HttpURLConnection}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+public class DefaultAdvancedHttpClient extends AdvancedHttpClient
+{
+
+ /** proxy authorization header */
+ @VisibleForTesting
+ static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
+
+ /** connection timeout */
+ @VisibleForTesting
+ static final int TIMEOUT_CONNECTION = 30000;
+
+ /** read timeout */
+ @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
+ */
+ private static final Logger logger =
+ LoggerFactory.getLogger(DefaultAdvancedHttpClient.class);
+
+ //~--- constructors ---------------------------------------------------------
+
+ /**
+ * Constructs a new {@link DefaultAdvancedHttpClient}.
+ *
+ *
+ * @param configuration scm-manager main configuration
+ * @param contentTransformers content transformer
+ */
+ @Inject
+ public DefaultAdvancedHttpClient(ScmConfiguration configuration,
+ Set contentTransformers)
+ {
+ this.configuration = configuration;
+ this.contentTransformers = contentTransformers;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * Creates a new {@link HttpURLConnection} from the given {@link URL}. The
+ * method is visible for testing.
+ *
+ *
+ * @param url url
+ *
+ * @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));
+ }
+
+ /**
+ * {@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.
+ *
+ *
+ * @param request http request
+ *
+ * @return server response
+ *
+ * @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(this, 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.addRequestProperty(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 = createConnection(url);
+ }
+
+ 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 createProxyConnecton(url, address);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ private boolean isProxyEnabled(BaseHttpRequest> request)
+ {
+ return !request.isIgnoreProxySettings()
+ && Proxies.isEnabled(configuration, request.getUrl());
+ }
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** 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
new file mode 100644
index 0000000000..21179ce1b6
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponse.java
@@ -0,0 +1,226 @@
+/**
+ * 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.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;
+import java.io.InputStream;
+
+import java.net.HttpURLConnection;
+
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * Http server response object of {@link DefaultAdvancedHttpClient}.
+ *
+ * @author Sebastian Sdorra
+ * @since 1.46
+ */
+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(DefaultAdvancedHttpClient client,
+ HttpURLConnection connection, int status, String statusText)
+ {
+ this.client = client;
+ this.connection = connection;
+ this.status = status;
+ this.statusText = statusText;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteSource contentAsByteSource() throws IOException
+ {
+ return new URLConnectionByteSource(connection);
+ }
+
+ //~--- get methods ----------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Multimap getHeaders()
+ {
+ if (headers == null)
+ {
+ headers = LinkedHashMultimap.create();
+
+ for (Entry> e :
+ connection.getHeaderFields().entrySet())
+ {
+ headers.putAll(e.getKey(), e.getValue());
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getStatus()
+ {
+ return status;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getStatusText()
+ {
+ return statusText;
+ }
+
+ //~--- methods --------------------------------------------------------------
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ContentTransformer createTransformer(Class> type,
+ String contentType)
+ {
+ return client.createTransformer(type, contentType);
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * {@link ByteSource} implementation of a http connection.
+ */
+ 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}.
+ *
+ *
+ * @param connection http connection
+ */
+ private URLConnectionByteSource(HttpURLConnection connection)
+ {
+ this.connection = connection;
+ }
+
+ //~--- methods ------------------------------------------------------------
+
+ /**
+ * Opens the input stream of http connection, if an error occurs during
+ * opening the method will return the error stream instead.
+ *
+ *
+ * @return input or error stream of http connection
+ *
+ * @throws IOException
+ */
+ @Override
+ public InputStream openStream() throws IOException
+ {
+ 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 -------------------------------------------------------------
+
+ /** http connection */
+ private final HttpURLConnection connection;
+ }
+
+
+ //~--- fields ---------------------------------------------------------------
+
+ /** Field description */
+ private final DefaultAdvancedHttpClient client;
+
+ /** http connection */
+ private final HttpURLConnection connection;
+
+ /** server response status */
+ private final int status;
+
+ /** server response text */
+ private final String statusText;
+
+ /** http headers */
+ private Multimap headers;
+}
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/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;
}
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..cd26af9d4b
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java
@@ -0,0 +1,375 @@
+/**
+ * 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 java.util.HashSet;
+import java.util.Set;
+
+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");
+ }
+
+ @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
+ *
+ *
+ * @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();
+ transformers = new HashSet();
+ client = new TestingAdvacedHttpClient(configuration, transformers);
+ }
+
+ //~--- inner classes --------------------------------------------------------
+
+ /**
+ * Class description
+ *
+ *
+ * @version Enter version here..., 15/05/01
+ * @author Enter your name here...
+ */
+ public class TestingAdvacedHttpClient extends DefaultAdvancedHttpClient
+ {
+
+ /**
+ * Constructs ...
+ *
+ *
+ * @param configuration
+ * @param transformers
+ */
+ public TestingAdvacedHttpClient(ScmConfiguration configuration,
+ Set transformers)
+ {
+ super(configuration, transformers);
+ }
+
+ //~--- 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;
+
+ /** 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
new file mode 100644
index 0000000000..f19c89eb5f
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java
@@ -0,0 +1,151 @@
+/**
+ * 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 sonia.scm.config.ScmConfiguration;
+
+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.HashSet;
+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(client,
+ connection, 200, "OK");
+ ByteSource content = response.contentAsByteSource();
+
+ 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(client,
+ connection, 404, "NOT FOUND");
+ 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(client,
+ connection, 200, "OK");
+ Multimap headers = response.getHeaders();
+
+ assertThat(headers.get("Test"), contains("One", "Two"));
+ assertTrue(headers.get("Test-2").isEmpty());
+ }
+
+ //~--- 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..1828c5ccf2
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/JsonContentTransformerTest.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 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