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 values) + { + for (Object v : values) + { + headers.put(name, toString(v)); + } + + return self(); + } + + /** + * Ignore proxy settings. The default value is false. + * + * + * @param ignoreProxySettings true to ignore proxy settings. + * + * @return request instance + */ + public T ignoreProxySettings(boolean ignoreProxySettings) + { + this.ignoreProxySettings = ignoreProxySettings; + + return self(); + } + + /** + * Appends a query parameter to the request. + * + * + * @param name name of query parameter + * @param values query parameter values + * + * @return request instance + */ + public T queryString(String name, Object... values) + { + for (Object v : values) + { + appendQueryString(name, v); + } + + return self(); + } + + /** + * Appends a query parameter to the request. + * + * + * @param name name of query parameter + * @param values query parameter values + * + * @return request instance + */ + public T queryStrings(String name, Iterable values) + { + for (Object v : values) + { + appendQueryString(name, v); + } + + return self(); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Return a map with http headers used for the request. + * + * + * @return map with http headers + */ + public Multimap getHeaders() + { + return headers; + } + + /** + * Returns the http method for the request. + * + * + * @return http method of request + */ + public String getMethod() + { + return method; + } + + /** + * Returns the url for the request. + * + * + * @return url of the request + */ + public String getUrl() + { + return url; + } + + /** + * Returns true if the request decodes gzip compression. + * + * + * @return true if the request decodes gzip compression + */ + public boolean isDecodeGZip() + { + return decodeGZip; + } + + /** + * Returns true if the verification of ssl certificates is disabled. + * + * + * @return true if certificate verification is disabled + */ + public boolean isDisableCertificateValidation() + { + return disableCertificateValidation; + } + + /** + * Returns true if the ssl hostname validation is disabled. + * + * + * @return true if the ssl hostname validation is disabled + */ + public boolean isDisableHostnameValidation() + { + return disableHostnameValidation; + } + + /** + * Returns true if the proxy settings are ignored. + * + * + * @return true if the proxy settings are ignored + */ + public boolean isIgnoreProxySettings() + { + return ignoreProxySettings; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Returns the value url encoded. + * + * + * @param value value to encode + * + * @return encoded value + */ + protected String encoded(Object value) + { + return HttpUtil.encode(Strings.nullToEmpty(toString(value))); + } + + /** + * Returns string representation of the given object or {@code null}, if the + * object is {@code null}. + * + * + * @param object object + * + * @return string representation or {@code null} + */ + protected String toString(Object object) + { + return (object != null) + ? object.toString() + : null; + } + + private void appendQueryString(String name, Object value) + { + StringBuilder buffer = new StringBuilder(); + + if (url.contains("?")) + { + buffer.append("&"); + } + else + { + buffer.append("?"); + } + + buffer.append(encoded(name)).append("=").append(encoded(value)); + url = url.concat(buffer.toString()); + } + + //~--- fields --------------------------------------------------------------- + + /** http client */ + protected final AdvancedHttpClient client; + + /** http header */ + private final Multimap headers = 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 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 i1 = Lists.newArrayList("b"); + Iterable i2 = Lists.newArrayList("d", "e"); + request.queryStrings("a", i1); + request.queryStrings("c", i2); + assertEquals("https://www.scm-manager.org?a=b&c=d&c=e", request.getUrl()); + } + + @Test + public void testQuerqStringNullValue(){ + request.queryString("a", null, "b"); + assertEquals("https://www.scm-manager.org?a=&a=b", request.getUrl()); + } + + @Test + public void testHeader(){ + request.header("a", "b"); + assertEquals("b", request.getHeaders().get("a").iterator().next()); + } + + @Test + public void testHeaderMultiple(){ + request.header("a", "b", "c", "d"); + Collection values = request.getHeaders().get("a"); + assertThat(values, contains("b", "c", "d")); + } + + @Test + public void testRequest() throws IOException{ + request.request(); + verify(ahc).request(request); + } + + @Test + public void testBuilderMethods(){ + Iterable i1 = Lists.newArrayList("b"); + assertThat(request.decodeGZip(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDecodeGZip()); + assertThat(request.disableCertificateValidation(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDisableCertificateValidation()); + assertThat(request.disableHostnameValidation(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isDisableHostnameValidation()); + assertThat(request.ignoreProxySettings(true), instanceOf(AdvancedHttpRequest.class)); + assertTrue(request.isIgnoreProxySettings()); + assertThat(request.header("a", "b"), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.headers("a", i1), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.queryString("a", "b"), instanceOf(AdvancedHttpRequest.class)); + assertThat(request.queryStrings("a", i1), instanceOf(AdvancedHttpRequest.class)); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java new file mode 100644 index 0000000000..24487f6582 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/ByteSourceContentTest.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class ByteSourceContentTest { + + @Mock + private AdvancedHttpRequestWithBody request; + + @Test + public void testPrepareRequest() throws IOException + { + ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8)); + ByteSourceContent content = new ByteSourceContent(source); + content.prepare(request); + verify(request).contentLength(3l); + } + + @Test + public void testProcess() throws IOException{ + ByteSource source = ByteSource.wrap("abc".getBytes(Charsets.UTF_8)); + ByteSourceContent content = new ByteSourceContent(source); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java new file mode 100644 index 0000000000..95b381b880 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/FileContentTest.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.net.ahc; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.io.Files; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.*; + +import static org.mockito.Mockito.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class FileContentTest +{ + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testPrepareRequest() throws IOException + { + FileContent content = create("abc"); + + content.prepare(request); + verify(request).contentLength(3l); + } + + /** + * Method description + * + * + * @throws IOException + */ + @Test + public void testProcess() throws IOException + { + FileContent content = create("abc"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + content.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + + private FileContent create(String value) throws IOException + { + File file = temp.newFile(); + + Files.write("abc", file, Charsets.UTF_8); + + return new FileContent(file); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + /** Field description */ + @Mock + private AdvancedHttpRequestWithBody request; +} diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java new file mode 100644 index 0000000000..56cc6d7379 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/FormContentBuilderTest.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.collect.Lists; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class FormContentBuilderTest { + + @Mock + private AdvancedHttpRequestWithBody request; + + @InjectMocks + private FormContentBuilder builder; + + @Test + public void testFieldEncoding() + { + builder.field("a", "ü", "ä", "ö").build(); + assertContent("a=%C3%BC&a=%C3%A4&a=%C3%B6"); + } + + @Test + public void testBuild() + { + builder.field("a", "b").build(); + assertContent("a=b"); + verify(request).contentType("application/x-www-form-urlencoded"); + } + + @Test + public void testFieldWithArray() + { + builder.field("a", "b").field("c", "d", "e").build(); + assertContent("a=b&c=d&c=e"); + } + + @Test + public void testFieldWithIterable() + { + Iterable i1 = Lists.newArrayList("b"); + builder.fields("a", i1) + .fields("c", Lists.newArrayList("d", "e")) + .build(); + assertContent("a=b&c=d&c=e"); + } + + private void assertContent(String content){ + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(request).stringContent(captor.capture()); + assertEquals(content, captor.getValue()); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java new file mode 100644 index 0000000000..f4ee8b7031 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/RawContentTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class RawContentTest { + + + @Mock + private AdvancedHttpRequestWithBody request; + + @Test + public void testPrepareRequest() + { + RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8)); + raw.prepare(request); + verify(request).contentLength(3); + } + + @Test + public void testProcess() throws IOException + { + RawContent raw = new RawContent("abc".getBytes(Charsets.UTF_8)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + raw.process(baos); + assertEquals("abc", baos.toString("UTF-8")); + } + +} \ No newline at end of file diff --git a/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java new file mode 100644 index 0000000000..bd191438ca --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/net/ahc/StringContentTest.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.net.ahc; + +import com.google.common.base.Charsets; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class StringContentTest { + + + @Test + public void testStringContent() + { + StringContent sc = new StringContent("abc", Charsets.UTF_8); + assertEquals("abc", new String(sc.getData())); + } + + @Test + public void testStringContentWithCharset() + { + StringContent sc = new StringContent("üäö", Charsets.ISO_8859_1); + assertEquals("üäö", new String(sc.getData(), Charsets.ISO_8859_1)); + } + +} \ No newline at end of file diff --git a/scm-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