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