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