added json and xml support to ahc

This commit is contained in:
Sebastian Sdorra
2015-05-03 15:51:21 +02:00
parent 722d2616a8
commit 1f4524bb20
19 changed files with 1222 additions and 81 deletions

View File

@@ -39,7 +39,7 @@ import java.io.IOException;
* Advanced client for http operations. The {@link AdvancedHttpClient} replaces
* the much more simpler implementation {@link sonia.scm.net.HttpClient}. The
* {@link AdvancedHttpClient} offers a fluid interface for handling most common
* http operations. The {@link AdvancedHttpClient} can be injected by the
* http operations. The {@link AdvancedHttpClient} can be injected by the
* default injection mechanism of SCM-Manager.
* <p>&nbsp;</p>
* <b>Http GET example:</b>
@@ -48,13 +48,13 @@ import java.io.IOException;
* AdvancedHttpResponse response = client.get("https://www.scm-manager.org")
* .decodeGZip(true)
* .request();
*
*
* System.out.println(response.contentAsString());
* </code></pre>
*
*
* <p>&nbsp;</p>
* <b>Http POST example:</b>
*
*
* <pre><code>
* AdvancedHttpResponse response = client.post("https://www.scm-manager.org")
* .formContent()
@@ -62,7 +62,7 @@ import java.io.IOException;
* .field("lastname", "McMillan")
* .build()
* .request();
*
*
* if (response.isSuccessful()){
* System.out.println("success");
* }
@@ -70,15 +70,28 @@ import java.io.IOException;
*
* @author Sebastian Sdorra
* @since 1.46
*
*
* @apiviz.landmark
*/
public abstract class AdvancedHttpClient
{
/**
* Creates a {@link ContentTransformer} for the given Content-Type.
*
* @param type object type
* @param contentType content-type
* @throws ContentTransformerNotFoundException if no
* {@link ContentTransformer} could be found for the content-type
*
* @return {@link ContentTransformer}
*/
protected abstract ContentTransformer createTransformer(Class<?> type,
String contentType);
/**
* Executes the given request and returns the http response. Implementation
* have to check, if the instance if from type
* have to check, if the instance if from type
* {@link AdvancedHttpRequestWithBody} in order to handle request contents.
*
*
@@ -118,8 +131,8 @@ public abstract class AdvancedHttpClient
}
/**
* Returns a request builder with a custom method. <strong>Note:</strong> not
* every method is supported by the underlying implementation of the http
* Returns a request builder with a custom method. <strong>Note:</strong> not
* every method is supported by the underlying implementation of the http
* client.
*
*

View File

@@ -122,6 +122,21 @@ public class AdvancedHttpRequestWithBody
return new FormContentBuilder(this);
}
/**
* Transforms the given object to a xml string and set this string as request
* content.
*
* @param object object to transform
* @throws ContentTransformerNotFoundException if no
* {@link ContentTransformer} could be found for the json content-type
*
* @return {@code this}
*/
public AdvancedHttpRequestWithBody jsonContent(Object object)
{
return transformedContent(ContentType.JSON, object);
}
/**
* Sets the raw data as request content.
*
@@ -182,6 +197,46 @@ public class AdvancedHttpRequestWithBody
return this;
}
/**
* Transforms the given object to a string and set this string as request
* content. The content-type is used to pick the right
* {@link ContentTransformer}. The method will throw an exception if no
* {@link ContentTransformer} for the content-type could be found.
*
* @param contentType content-type
* @param object object to transform
* @throws ContentTransformerNotFoundException if no
* {@link ContentTransformer} could be found for the given content-type
*
* @return {@code this}
*/
public AdvancedHttpRequestWithBody transformedContent(String contentType,
Object object)
{
ContentTransformer transformer =
client.createTransformer(object.getClass(), contentType);
ByteSource value = transformer.marshall(object);
contentType(contentType);
return rawContent(value);
}
/**
* Transforms the given object to a xml string and set this string as request
* content.
*
* @param object object to transform
* @throws ContentTransformerNotFoundException if no
* {@link ContentTransformer} could be found for the xml content-type
*
* @return {@code this}
*/
public AdvancedHttpRequestWithBody xmlContent(Object object)
{
return transformedContent(ContentType.XML, object);
}
//~--- get methods ----------------------------------------------------------
/**

View File

@@ -34,6 +34,7 @@ package sonia.scm.net.ahc;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteSource;
@@ -45,7 +46,7 @@ import java.io.IOException;
import java.io.InputStream;
/**
* Http response. The response of a {@link AdvancedHttpRequest} or
* Http response. The response of a {@link AdvancedHttpRequest} or
* {@link AdvancedHttpRequestWithBody}.
*
* @author Sebastian Sdorra
@@ -54,8 +55,56 @@ import java.io.InputStream;
public abstract class AdvancedHttpResponse
{
/**
* Returns the response content as byte source.
*
*
* @return response content as byte source
* @throws IOException
*/
public abstract ByteSource contentAsByteSource() throws IOException;
//~--- get methods ----------------------------------------------------------
/**
* Returns the response headers.
*
*
* @return response headers
*/
public abstract Multimap<String, String> getHeaders();
/**
* Returns the status code of the response.
*
*
* @return status code
*/
public abstract int getStatus();
/**
* Returns the status text of the response.
*
*
* @return status text
*/
public abstract String getStatusText();
//~--- methods --------------------------------------------------------------
/**
* Creates a {@link ContentTransformer} for the given Content-Type.
*
* @param type object type
* @param contentType content-type
* @throws ContentTransformerNotFoundException if no
* {@link ContentTransformer} could be found for the content-type
*
* @return {@link ContentTransformer}
*/
protected abstract ContentTransformer createTransformer(Class<?> type,
String contentType);
/**
* Returns the content of the response as byte array.
*
@@ -89,6 +138,7 @@ public abstract class AdvancedHttpResponse
{
ByteSource content = contentAsByteSource();
BufferedReader reader = null;
if (content != null)
{
reader = content.asCharSource(Charsets.UTF_8).openBufferedStream();
@@ -109,6 +159,7 @@ public abstract class AdvancedHttpResponse
{
ByteSource content = contentAsByteSource();
InputStream stream = null;
if (content != null)
{
stream = content.openBufferedStream();
@@ -116,15 +167,6 @@ public abstract class AdvancedHttpResponse
return stream;
}
/**
* Returns the response content as byte source.
*
*
* @return response content as byte source
* @throws IOException
*/
public abstract ByteSource contentAsByteSource() throws IOException;
/**
* Returns the response content as string.
@@ -138,6 +180,7 @@ public abstract class AdvancedHttpResponse
{
ByteSource content = contentAsByteSource();
String value = null;
if (content != null)
{
value = content.asCharSource(Charsets.UTF_8).read();
@@ -146,6 +189,99 @@ public abstract class AdvancedHttpResponse
return value;
}
/**
* Transforms the response content from json to the given type.
*
* @param <T> 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> T contentFromJson(Class<T> type) throws IOException
{
return contentTransformed(type, ContentType.JSON);
}
/**
* Transforms the response content from xml to the given type.
*
* @param <T> 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> T contentFromXml(Class<T> 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 <T> 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> T contentTransformed(Class<T> 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 <T> 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> T contentTransformed(Class<T> type, String contentType)
throws IOException
{
T object = null;
ByteSource source = contentAsByteSource();
if (source != null)
{
ContentTransformer transformer = createTransformer(type, contentType);
object = transformer.unmarshall(type, contentAsByteSource());
}
return object;
}
//~--- get methods ----------------------------------------------------------
/**
@@ -162,38 +298,15 @@ public abstract class AdvancedHttpResponse
}
/**
* Returns the response headers.
*
*
* @return response headers
*/
public abstract Multimap<String, String> getHeaders();
/**
* Returns {@code true} if the response was successful. A response is
* Returns {@code true} if the response was successful. A response is
* successful, if the status code is greater than 199 and lower than 400.
*
*
* @return {@code true} if the response was successful
*/
public boolean isSuccessful()
{
int status = getStatus();
return status > 199 && status < 400;
}
/**
* Returns the status code of the response.
*
*
* @return status code
*/
public abstract int getStatus();
/**
* Returns the status text of the response.
*
*
* @return status text
*/
public abstract String getStatusText();
return (status > 199) && (status < 400);
}
}

View File

@@ -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 <T> type of result object
*
* @return
*/
public <T> T unmarshall(Class<T> type, ByteSource content);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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() {}
}