You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wink.apache.org by bl...@apache.org on 2010/11/03 22:38:09 UTC

svn commit: r1030701 - in /incubator/wink/trunk: ./ wink-client-asynchttpclient/ wink-client-asynchttpclient/src/ wink-client-asynchttpclient/src/main/ wink-client-asynchttpclient/src/main/java/ wink-client-asynchttpclient/src/main/java/org/ wink-clien...

Author: bluk
Date: Wed Nov  3 21:38:09 2010
New Revision: 1030701

URL: http://svn.apache.org/viewvc?rev=1030701&view=rev
Log:
Add Wink asynchronous HTTP client support

See [WINK-326]

Thanks to Jean-Francois Arcand and
Jason Dillon for the contribution.

Added:
    incubator/wink/trunk/wink-client-asynchttpclient/
    incubator/wink/trunk/wink-client-asynchttpclient/pom.xml
    incubator/wink/trunk/wink-client-asynchttpclient/src/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConfiguration.java
    incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConnectionHandler.java
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/AsyncClientTest.java
    incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/ClientTestSupport.java
Modified:
    incubator/wink/trunk/pom.xml

Modified: incubator/wink/trunk/pom.xml
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/pom.xml?rev=1030701&r1=1030700&r2=1030701&view=diff
==============================================================================
--- incubator/wink/trunk/pom.xml (original)
+++ incubator/wink/trunk/pom.xml Wed Nov  3 21:38:09 2010
@@ -42,6 +42,7 @@
         <module>wink-server</module>
         <module>wink-client</module>
         <module>wink-client-apache-httpclient</module>
+        <module>wink-client-asynchttpclient</module>
         <module>wink-spring-support</module>
         <module>wink-webdav</module>
 	    <module>wink-json4j</module>
@@ -490,6 +491,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.wink</groupId>
+                <artifactId>wink-client-asynchttpclient</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.wink</groupId>
                 <artifactId>wink-abdera-provider</artifactId>
                 <version>${project.version}</version>
             </dependency>

Added: incubator/wink/trunk/wink-client-asynchttpclient/pom.xml
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-client-asynchttpclient/pom.xml?rev=1030701&view=auto
==============================================================================
--- incubator/wink/trunk/wink-client-asynchttpclient/pom.xml (added)
+++ incubator/wink/trunk/wink-client-asynchttpclient/pom.xml Wed Nov  3 21:38:09 2010
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>wink-client-asynchttpclient</artifactId>
+    <name>Apache Wink Client :: Async Http Client</name>
+    <parent>
+        <groupId>org.apache.wink</groupId>
+        <artifactId>wink</artifactId>
+        <version>1.1.2-incubating-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.wink</groupId>
+            <artifactId>wink-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ning</groupId>
+            <artifactId>async-http-client</artifactId>
+            <version>1.3.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.wink</groupId>
+            <artifactId>wink-component-test-support</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jmock</groupId>
+            <artifactId>jmock-junit3</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

Added: incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConfiguration.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConfiguration.java?rev=1030701&view=auto
==============================================================================
--- incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConfiguration.java (added)
+++ incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConfiguration.java Wed Nov  3 21:38:09 2010
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ *******************************************************************************/
+
+package org.sonatype.wink.client;
+
+import com.ning.http.client.AsyncHttpClient;
+import org.apache.wink.client.ClientConfig;
+import org.apache.wink.client.handlers.ConnectionHandler;
+
+/**
+ * Configure the {@link AsyncHttpClient}
+ */
+public class AsyncHttpClientConfiguration
+    extends ClientConfig
+{
+    private AsyncHttpClient client;
+
+    public AsyncHttpClientConfiguration() {
+        this(null);
+    }
+
+    public AsyncHttpClientConfiguration(final AsyncHttpClient client) {
+        this.client = client;
+    }
+
+    @Override
+    protected ConnectionHandler getConnectionHandler() {
+        return new AsyncHttpClientConnectionHandler(client);
+    }
+}

Added: incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConnectionHandler.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConnectionHandler.java?rev=1030701&view=auto
==============================================================================
--- incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConnectionHandler.java (added)
+++ incubator/wink/trunk/wink-client-asynchttpclient/src/main/java/org/sonatype/wink/client/AsyncHttpClientConnectionHandler.java Wed Nov  3 21:38:09 2010
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ *******************************************************************************/
+
+package org.sonatype.wink.client;
+
+import com.ning.http.client.AsyncCompletionHandlerBase;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.ning.http.client.FluentCaseInsensitiveStringsMap;
+import com.ning.http.client.ProxyServer;
+import com.ning.http.client.Request;
+import com.ning.http.client.RequestBuilder;
+import com.ning.http.client.Response;
+import org.apache.wink.client.ClientConfig;
+import org.apache.wink.client.ClientRequest;
+import org.apache.wink.client.ClientResponse;
+import org.apache.wink.client.handlers.HandlerContext;
+import org.apache.wink.client.internal.handlers.AbstractConnectionHandler;
+import org.apache.wink.client.internal.handlers.ClientResponseImpl;
+import org.apache.wink.common.internal.WinkConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.MultivaluedMap;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Extends {@link AbstractConnectionHandler} and uses {@link AsyncHttpClient} to perform HTTP request execution.
+ */
+public class AsyncHttpClientConnectionHandler
+    extends AbstractConnectionHandler
+{
+    private static final Logger logger = LoggerFactory.getLogger(AsyncHttpClientConnectionHandler.class);
+
+    private AsyncHttpClient asyncHttpClient;
+
+    public AsyncHttpClientConnectionHandler(final AsyncHttpClient asyncHttpClient) {
+        this.asyncHttpClient = asyncHttpClient;
+    }
+
+    public ClientResponse handle(final ClientRequest request, final HandlerContext context) throws Exception {
+        Response response = processRequest(request, context);
+        return processResponse(request, context, response);
+    }
+
+    private Response processRequest(final ClientRequest cr, final HandlerContext context) throws IOException {
+        AsyncHttpClient asyncHttpClient = openConnection(cr);
+        NonCloseableOutputStream ncos = new NonCloseableOutputStream();
+        OutputStream os = adaptOutputStream(ncos, cr, context.getOutputStreamAdapters());
+
+        Request request = setupHttpRequest(cr, ncos, os);
+        Response response;
+
+        try {
+            response = asyncHttpClient.executeRequest(request, new AsyncCompletionHandlerBase()
+            {
+                @Override
+                public Response onCompleted(final Response response) throws Exception {
+                    logger.debug("Response received: {}", response);
+                    return super.onCompleted(response);
+                }
+
+                public void onThrowable(Throwable t) {
+                    logger.error(AsyncCompletionHandlerBase.class.getName(), t);
+                }
+            }).get();
+        }
+        catch (InterruptedException e) {
+            throw (IOException)new IOException().initCause(e);
+        }
+        catch (ExecutionException e) {
+            throw (IOException)new IOException().initCause(e);
+        }
+
+        return response;
+    }
+
+    private Request setupHttpRequest(final ClientRequest cr, final NonCloseableOutputStream ncos, final OutputStream adaptedOutputStream) {
+        URI uri = cr.getURI();
+        String method = cr.getMethod();
+        RequestBuilder builder = new RequestBuilder(method);
+        builder.setUrl(uri.toString());
+
+        MultivaluedMap<String, String> headers = cr.getHeaders();
+        for (String header : headers.keySet()) {
+            List<String> values = headers.get(header);
+            for (String value : values) {
+                if (value != null) {
+                    builder.addHeader(header, value);
+                }
+            }
+        }
+
+        if (method.equalsIgnoreCase("PUT") || method.equalsIgnoreCase("POST")) {
+            builder.setBody(new Request.EntityWriter()
+            {
+                public void writeEntity(OutputStream os) throws IOException {
+                    ncos.setOutputStream(os);
+                    AsyncHttpClientConnectionHandler.this.writeEntity(cr, adaptedOutputStream);
+                }
+            });
+        }
+
+        return builder.build();
+    }
+
+    private AsyncHttpClient openConnection(final ClientRequest request) {
+        if (asyncHttpClient != null) {
+            return asyncHttpClient;
+        }
+
+        // cast is safe because we're on the client
+        ClientConfig config = (ClientConfig) request.getAttribute(WinkConfiguration.class);
+
+        AsyncHttpClientConfig.Builder c = new AsyncHttpClientConfig.Builder();
+        c.setConnectionTimeoutInMs(config.getConnectTimeout());
+        c.setRequestTimeoutInMs(config.getReadTimeout());
+        c.setFollowRedirects(config.isFollowRedirects());
+
+        // setup proxy
+        if (config.getProxyHost() != null) {
+            c.setProxyServer(new ProxyServer(config.getProxyHost(), config.getProxyPort()));
+        }
+
+        return new AsyncHttpClient(c.build());
+    }
+
+    private ClientResponse processResponse(final ClientRequest request, final HandlerContext context, final Response response)
+        throws IllegalStateException, IOException
+    {
+        ClientResponse cr = createResponse(request, response);
+        InputStream is = adaptInputStream(response.getResponseBodyAsStream(), cr, context.getInputStreamAdapters());
+        cr.setEntity(is);
+        return cr;
+    }
+
+    private ClientResponse createResponse(final ClientRequest request, final Response response) {
+        final ClientResponseImpl cr = new ClientResponseImpl();
+        cr.setStatusCode(response.getStatusCode());
+        cr.setMessage(response.getStatusText());
+        cr.getAttributes().putAll(request.getAttributes());
+
+        // FIXME: Should we use a constant here to avoid creating this dummy runnable every time?
+        cr.setContentConsumer(new Runnable()
+        {
+            public void run() {
+                // empty
+            }
+        });
+
+        processResponseHeaders(cr, response);
+
+        return cr;
+    }
+
+    private void processResponseHeaders(final ClientResponse cr, final Response response) {
+        FluentCaseInsensitiveStringsMap headers = response.getHeaders();
+        for (Map.Entry<String, List<String>> header : headers) {
+            for (String value : header.getValue()) {
+                cr.getHeaders().add(header.getKey(), value);
+            }
+        }
+    }
+
+    // TODO: move this class to the base class
+
+    private static class NonCloseableOutputStream
+        extends OutputStream
+    {
+        OutputStream os;
+
+        public NonCloseableOutputStream() {
+        }
+
+        public void setOutputStream(final OutputStream os) {
+            this.os = os;
+        }
+
+        @Override
+        public void close() throws IOException {
+            // do nothing
+        }
+
+        @Override
+        public void flush() throws IOException {
+            os.flush();
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            os.write(b, off, len);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            os.write(b);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            os.write(b);
+        }
+    }
+}

Added: incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/AsyncClientTest.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/AsyncClientTest.java?rev=1030701&view=auto
==============================================================================
--- incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/AsyncClientTest.java (added)
+++ incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/AsyncClientTest.java Wed Nov  3 21:38:09 2010
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ *******************************************************************************/
+
+package org.apache.wink.client;
+
+import org.sonatype.wink.client.AsyncHttpClientConfiguration;
+
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AsyncClientTest
+    extends ClientTestSupport
+{
+    protected RestClient getRestClient() {
+        return new RestClient(new AsyncHttpClientConfiguration().applications(new Application() {
+            @Override
+            public Set<Class<?>> getClasses() {
+                Set<Class<?>> set = new HashSet<Class<?>>();
+                set.add(TestGenericsProvider.class);
+                return set;
+            }
+
+        }));
+    }
+}

Added: incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/ClientTestSupport.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/ClientTestSupport.java?rev=1030701&view=auto
==============================================================================
--- incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/ClientTestSupport.java (added)
+++ incubator/wink/trunk/wink-client-asynchttpclient/src/test/java/org/apache/wink/client/ClientTestSupport.java Wed Nov  3 21:38:09 2010
@@ -0,0 +1,309 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ *******************************************************************************/
+
+package org.apache.wink.client;
+
+import org.apache.wink.client.MockHttpServer.MockHttpServerResponse;
+import org.apache.wink.common.RuntimeContext;
+import org.apache.wink.common.internal.WinkConfiguration;
+import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
+import org.apache.wink.common.utils.ProviderUtils;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+
+/**
+ * Support for client tests.
+ */
+public abstract class ClientTestSupport
+    extends BaseTest
+{
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        Mockery mockery = new Mockery();
+        final RuntimeContext context = mockery.mock(RuntimeContext.class);
+        mockery.checking(new Expectations() {{
+            allowing(context).getAttribute(WinkConfiguration.class); will(returnValue(null));
+        }});
+
+        RuntimeContextTLS.setRuntimeContext(context);
+    }
+
+    @Override
+    public void tearDown() {
+        RuntimeContextTLS.setRuntimeContext(null);
+    }
+
+    public static class TestGenerics<T> {
+        private T t;
+
+        public TestGenerics() {
+        }
+
+        public T getT() {
+            return t;
+        }
+
+        public void setT(T t) {
+            this.t = t;
+        }
+    }
+
+    @Provider
+    public static class TestGenericsProvider
+        implements MessageBodyWriter<TestGenerics<String>>,MessageBodyReader<TestGenerics<String>>
+    {
+        public long getSize(TestGenerics<String> t,
+                            Class<?> type,
+                            Type genericType,
+                            Annotation[] annotations,
+                            MediaType mediaType) {
+            return t.getT().length();
+        }
+
+        public boolean isWriteable(Class<?> type,
+                                   Type genericType,
+                                   Annotation[] annotations,
+                                   MediaType mediaType) {
+            return isTestGenericsString(type, genericType);
+        }
+
+        private boolean isTestGenericsString(Class<?> type, Type genericType) {
+            return type.equals(TestGenerics.class) && genericType instanceof ParameterizedType
+                && ((ParameterizedType)genericType).getActualTypeArguments()[0]
+                    .equals(String.class);
+        }
+
+        public void writeTo(TestGenerics<String> t,
+                            Class<?> type,
+                            Type genericType,
+                            Annotation[] annotations,
+                            MediaType mediaType,
+                            MultivaluedMap<String, Object> httpHeaders,
+                            OutputStream entityStream) throws IOException, WebApplicationException {
+            String string = t.getT();
+            ProviderUtils.writeToStream(string, entityStream, mediaType);
+        }
+
+        public boolean isReadable(Class<?> type,
+                                  Type genericType,
+                                  Annotation[] annotations,
+                                  MediaType mediaType) {
+            return isTestGenericsString(type, genericType);
+        }
+
+        public TestGenerics<String> readFrom(Class<TestGenerics<String>> type,
+                                             Type genericType,
+                                             Annotation[] annotations,
+                                             MediaType mediaType,
+                                             MultivaluedMap<String, String> httpHeaders,
+                                             InputStream entityStream) throws IOException,
+            WebApplicationException {
+            TestGenerics<String> tg = new TestGenerics<String>();
+            String string = ProviderUtils.readFromStreamAsString(entityStream, mediaType);
+            tg.setT(string);
+            return tg;
+        }
+    }
+
+    protected abstract RestClient getRestClient();
+    
+    public void testResourceGet() {
+        MockHttpServerResponse response1 = new MockHttpServerResponse();
+        response1.setMockResponseCode(200);
+        MockHttpServerResponse response2 = new MockHttpServerResponse();
+        response2.setMockResponseCode(200);
+        MockHttpServerResponse response3 = new MockHttpServerResponse();
+        response3.setMockResponseCode(200);
+        server.setMockHttpServerResponses(response1, response2, response3);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL);
+
+        String string = resource.get(String.class);
+        assertEquals(RECEIVED_MESSAGE, string);
+
+        // do get with response
+        ClientResponse clientResponse = resource.get();
+        assertEquals(RECEIVED_MESSAGE, clientResponse.getEntity(String.class));
+
+        // test generic entity
+        TestGenerics<String> tg = resource.get(new EntityType<TestGenerics<String>>() {});
+        assertEquals(RECEIVED_MESSAGE, tg.getT());
+    }
+
+    public void testResourcePut() throws IOException {
+        MockHttpServerResponse response1 = new MockHttpServerResponse();
+        response1.setMockResponseCode(200);
+        MockHttpServerResponse response2 = new MockHttpServerResponse();
+        response2.setMockResponseCode(200);
+        MockHttpServerResponse response3 = new MockHttpServerResponse();
+        response3.setMockResponseCode(200);
+        server.setMockHttpServerResponses(response1, response2, response3);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL + "/testResourcePut");
+        String response =
+            resource.contentType("text/plain").accept("text/plain").put(String.class, SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, response);
+        assertEquals(SENT_MESSAGE, server.getRequestContentAsString());
+
+        // do put with response
+        ClientResponse clientResponse = resource.put(SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, clientResponse.getEntity(String.class));
+
+        // test generic entity
+        TestGenerics<String> tg = resource.put(new EntityType<TestGenerics<String>>() {
+        }, SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, tg.getT());
+
+    }
+
+    public void testResourcePost() throws IOException {
+        MockHttpServerResponse response1 = new MockHttpServerResponse();
+        response1.setMockResponseCode(200);
+        MockHttpServerResponse response2 = new MockHttpServerResponse();
+        response2.setMockResponseCode(200);
+        MockHttpServerResponse response3 = new MockHttpServerResponse();
+        response3.setMockResponseCode(200);
+        server.setMockHttpServerResponses(response1, response2, response3);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL + "/testResourcePost");
+        String response =
+            resource.contentType("text/plain").accept("text/plain")
+                .post(String.class, SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, response);
+        assertEquals(SENT_MESSAGE, server.getRequestContentAsString());
+
+        // do post with response
+        ClientResponse clientResponse = resource.post(SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, clientResponse.getEntity(String.class));
+
+        // test generic entity
+        TestGenerics<String> tg = resource.post(new EntityType<TestGenerics<String>>() {
+        }, SENT_MESSAGE);
+        assertEquals(RECEIVED_MESSAGE, tg.getT());
+    }
+
+    public void testResourceDelete() {
+        MockHttpServerResponse response1 = new MockHttpServerResponse();
+        response1.setMockResponseCode(200);
+        MockHttpServerResponse response2 = new MockHttpServerResponse();
+        response2.setMockResponseCode(200);
+        MockHttpServerResponse response3 = new MockHttpServerResponse();
+        response3.setMockResponseCode(200);
+        server.setMockHttpServerResponses(response1, response2, response3);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL);
+        String response = resource.accept(MediaType.TEXT_PLAIN_TYPE).delete(String.class);
+        assertEquals(RECEIVED_MESSAGE, response);
+
+        // do delete with response
+        ClientResponse clientResponse = resource.delete();
+        assertEquals(RECEIVED_MESSAGE, clientResponse.getEntity(String.class));
+
+        // test generic entity
+        TestGenerics<String> tg = resource.delete(new EntityType<TestGenerics<String>>() {
+        });
+        assertEquals(RECEIVED_MESSAGE, tg.getT());
+    }
+
+    public void testInvoke() {
+        MockHttpServerResponse response1 = new MockHttpServerResponse();
+        response1.setMockResponseCode(200);
+        MockHttpServerResponse response2 = new MockHttpServerResponse();
+        response2.setMockResponseCode(200);
+        server.setMockHttpServerResponses(response1, response2);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL);
+
+        String string = resource.invoke("GET", String.class, null);
+        assertEquals(RECEIVED_MESSAGE, string);
+
+        // test generic entity
+        TestGenerics<String> tg = resource.invoke("GET", new EntityType<TestGenerics<String>>() {
+        }, null);
+        assertEquals(RECEIVED_MESSAGE, tg.getT());
+    }
+
+    public void testHttpErrorNoResponse() throws IOException {
+        server.getMockHttpServerResponses().get(0).setMockResponseCode(400);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL);
+        try {
+            resource.accept("text/plain").invoke("GET", String.class, null);
+            fail("ClientWebException must be thrown");
+        } catch (ClientWebException e) {
+            assertTrue(e.getResponse().getStatusCode() == 400);
+        }
+    }
+
+    public void testHttpErrorWithResponse() throws IOException {
+        server.getMockHttpServerResponses().get(0).setMockResponseCode(400);
+        
+        RestClient client = getRestClient();
+        Resource resource = client.resource(serviceURL);
+        try {
+            ClientResponse res = resource.accept("text/plain").get();
+            assertTrue(res.getStatusCode() == 400);
+        } catch (Exception e) {
+            fail("Exception must not be thrown");
+        }
+    }
+
+    public void testResponseCharset() throws IOException {
+
+        MockHttpServer server = new MockHttpServer(34567);
+        server.getMockHttpServerResponses().get(0).setMockResponseCode(200);
+        server.getMockHttpServerResponses().get(0).setMockResponseContent("REQUEST".getBytes("UTF-16"));
+        server.getMockHttpServerResponses().get(0).setMockResponseContentType("text/plain; charset=UTF-16");
+
+        server.startServer();
+        try {
+            RestClient client = getRestClient();
+            Resource resource =
+                client.resource(MessageFormat.format(SERVICE_URL, String.valueOf(server
+                    .getServerPort())));
+            String response = resource.accept(MediaType.TEXT_PLAIN_TYPE).get(String.class);
+            assertEquals("REQUEST", response);
+
+        } finally {
+            server.stopServer();
+        }
+    }
+}