You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2010/02/11 19:41:37 UTC

svn commit: r909102 - in /cxf/trunk: rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/ rt/frontend/jaxrs/src/main/resources/schemas/ systests/jaxrs/src/test/java/org/apache/cxf/sy...

Author: sergeyb
Date: Thu Feb 11 18:41:34 2010
New Revision: 909102

URL: http://svn.apache.org/viewvc?rev=909102&view=rev
Log:
JAXRS : making proxies and webclients optionally thread-safe

Added:
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java   (with props)
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java   (with props)
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java   (with props)
    cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java   (with props)
Modified:
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java
    cxf/trunk/rt/frontend/jaxrs/src/main/resources/schemas/jaxrs.xsd
    cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java Thu Feb 11 18:41:34 2010
@@ -83,16 +83,14 @@
     private static final String RESPONSE_CONTEXT = "ResponseContext";
     
     protected ClientConfiguration cfg = new ClientConfiguration();
+    private ClientState state;
     
-    private MultivaluedMap<String, String> requestHeaders = new MetadataMap<String, String>();
-    private ResponseBuilder responseBuilder;
+    protected AbstractClient(URI baseURI) {
+        this.state = new LocalClientState(baseURI);
+    }
     
-    private URI baseURI;
-    private UriBuilder currentBuilder;
-
-    protected AbstractClient(URI baseURI, URI currentURI) {
-        this.baseURI = baseURI;
-        this.currentBuilder = new UriBuilderImpl(currentURI);
+    protected AbstractClient(ClientState initialState) {
+        this.state = initialState;
     }
     
     /**
@@ -102,20 +100,25 @@
         if (values == null) {
             throw new IllegalArgumentException();
         }
-        if (HttpHeaders.CONTENT_TYPE.equals(name) && values.length > 1) {
-            throw new WebApplicationException();
-        }
-        for (Object o : values) {
-            requestHeaders.add(name, o.toString());
+        if (HttpHeaders.CONTENT_TYPE.equals(name)) {
+            if (values.length > 1) {
+                throw new IllegalArgumentException("Content-Type can have a single value only");
+            }
+            type(values[0].toString());
+        } else {
+            for (Object o : values) {
+                possiblyAddHeader(name, o.toString());
+            }
         }
         return this;
     }
 
+    
     /**
      * {@inheritDoc}
      */
     public Client headers(MultivaluedMap<String, String> map) {
-        requestHeaders.putAll(map);
+        state.getRequestHeaders().putAll(map);
         return this;
     }
     
@@ -124,7 +127,7 @@
      */
     public Client accept(MediaType... types) {
         for (MediaType mt : types) {
-            requestHeaders.add(HttpHeaders.ACCEPT, mt.toString());
+            possiblyAddHeader(HttpHeaders.ACCEPT, mt.toString());
         }
         return this;
     }
@@ -140,7 +143,7 @@
      * {@inheritDoc}
      */
     public Client type(String type) {
-        requestHeaders.putSingle(HttpHeaders.CONTENT_TYPE, type);
+        state.getRequestHeaders().putSingle(HttpHeaders.CONTENT_TYPE, type);
         return this;
     }
 
@@ -149,7 +152,7 @@
      */
     public Client accept(String... types) {
         for (String type : types) {
-            requestHeaders.add(HttpHeaders.ACCEPT, type);
+            possiblyAddHeader(HttpHeaders.ACCEPT, type);
         }
         return this;
     }
@@ -158,7 +161,7 @@
      * {@inheritDoc}
      */
     public Client cookie(Cookie cookie) {
-        requestHeaders.add(HttpHeaders.COOKIE, cookie.toString());
+        possiblyAddHeader(HttpHeaders.COOKIE, cookie.toString());
         return this;
     }
 
@@ -168,7 +171,7 @@
     public Client modified(Date date, boolean ifNot) {
         SimpleDateFormat dateFormat = HttpUtils.getHttpDateFormat();
         String hName = ifNot ? HttpHeaders.IF_UNMODIFIED_SINCE : HttpHeaders.IF_MODIFIED_SINCE;
-        requestHeaders.putSingle(hName, dateFormat.format(date));
+        state.getRequestHeaders().putSingle(hName, dateFormat.format(date));
         return this;
     }
 
@@ -176,7 +179,7 @@
      * {@inheritDoc}
      */
     public Client language(String language) {
-        requestHeaders.putSingle(HttpHeaders.CONTENT_LANGUAGE, language);
+        state.getRequestHeaders().putSingle(HttpHeaders.CONTENT_LANGUAGE, language);
         return this;
     }
 
@@ -185,7 +188,7 @@
      */
     public Client match(EntityTag tag, boolean ifNot) {
         String hName = ifNot ? HttpHeaders.IF_NONE_MATCH : HttpHeaders.IF_MATCH; 
-        requestHeaders.putSingle(hName, tag.toString());
+        state.getRequestHeaders().putSingle(hName, tag.toString());
         return this;
     }
 
@@ -194,7 +197,7 @@
      */
     public Client acceptLanguage(String... languages) {
         for (String s : languages) {
-            requestHeaders.add(HttpHeaders.ACCEPT_LANGUAGE, s);
+            possiblyAddHeader(HttpHeaders.ACCEPT_LANGUAGE, s);
         }
         return this;
     }
@@ -204,7 +207,7 @@
      */
     public Client acceptEncoding(String... encs) {
         for (String s : encs) {
-            requestHeaders.add(HttpHeaders.ACCEPT_ENCODING, s);
+            possiblyAddHeader(HttpHeaders.ACCEPT_ENCODING, s);
         }
         return this;
     }
@@ -213,7 +216,7 @@
      * {@inheritDoc}
      */
     public Client encoding(String enc) {
-        requestHeaders.putSingle(HttpHeaders.CONTENT_ENCODING, enc);
+        state.getRequestHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, enc);
         return this;
     }
 
@@ -222,7 +225,7 @@
      */
     public MultivaluedMap<String, String> getHeaders() {
         MultivaluedMap<String, String> map = new MetadataMap<String, String>();
-        map.putAll(requestHeaders);
+        map.putAll(state.getRequestHeaders());
         return map;
     }
     
@@ -230,7 +233,7 @@
      * {@inheritDoc}
      */
     public URI getBaseURI() {
-        return baseURI;
+        return state.getBaseURI();
     }
     
     /**
@@ -244,37 +247,50 @@
      * {@inheritDoc}
      */
     public Response getResponse() {
-        if (responseBuilder == null) {
+        if (state.getResponseBuilder() == null) {
             return null;
         }
-        return responseBuilder.build();
+        return state.getResponseBuilder().build();
     }
     
     /**
      * {@inheritDoc}
      */
     public Client reset() {
-        requestHeaders.clear();
-        resetResponse();
+        state.reset();
         return this;
     }
 
+    private void possiblyAddHeader(String name, String value) {
+        if (!isDuplicate(name, value)) {
+            state.getRequestHeaders().add(name, value);
+        }
+    }
+    
+    private boolean isDuplicate(String name, String value) {
+        List<String> values = state.getRequestHeaders().get(name);
+        return values != null && values.contains(value) ? true : false;
+    }
+    
+    protected ClientState getState() {
+        return state;
+    }
     
     protected UriBuilder getCurrentBuilder() {
-        return currentBuilder;
+        return state.getCurrentBuilder();
     }
 
     protected void resetResponse() {
-        responseBuilder = null;
+        state.setResponseBuilder(null);
     }
     
     protected void resetBaseAddress(URI uri) {
-        baseURI = uri;
+        state.setBaseURI(uri);
         resetCurrentBuilder(uri);
     }
     
     protected void resetCurrentBuilder(URI uri) {
-        currentBuilder = new UriBuilderImpl(uri);
+        state.setCurrentBuilder(new UriBuilderImpl(uri));
     }
     
     protected ResponseBuilder setResponseBuilder(HttpURLConnection conn, Exchange exchange) throws Throwable {
@@ -297,19 +313,20 @@
             }
         } 
         int status = responseCode.intValue();
-        responseBuilder = Response.status(status);
+        ResponseBuilder currentResponseBuilder = Response.status(status);
+        
         for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
             if (null == entry.getKey()) {
                 continue;
             }
             if (HttpUtils.isDateRelatedHeader(entry.getKey())) {
-                responseBuilder.header(entry.getKey(), entry.getValue());
+                currentResponseBuilder.header(entry.getKey(), entry.getValue());
             } else if (entry.getValue().size() > 0) {
                 String[] values = entry.getValue().get(0).split(",");
                 for (String s : values) {
                     String theValue = s.trim();
                     if (theValue.length() > 0) {
-                        responseBuilder.header(entry.getKey(), theValue);
+                        currentResponseBuilder.header(entry.getKey(), theValue);
                     }
                 }
             }
@@ -321,19 +338,21 @@
         if (status >= 400) {
             try {
                 InputStream errorStream = mStream == null ? conn.getErrorStream() : mStream;
-                responseBuilder.entity(errorStream);
+                currentResponseBuilder.entity(errorStream);
             } catch (Exception ex) {
                 // nothing we can do really
             }
         } else {
             try {
                 InputStream stream = mStream == null ? conn.getInputStream() : mStream;
-                responseBuilder.entity(stream);
+                currentResponseBuilder.entity(stream);
             } catch (Exception ex) {
                 // it may that the successful response has no response body
             }
         }
-        return responseBuilder;
+        ResponseBuilder rb = currentResponseBuilder.clone();
+        state.setResponseBuilder(currentResponseBuilder);
+        return rb;
     }
 
     @SuppressWarnings("unchecked")

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java Thu Feb 11 18:41:34 2010
@@ -76,9 +76,18 @@
     private boolean isRoot;
     private Map<String, Object> valuesMap;
     
-    public ClientProxyImpl(URI baseURI, URI currentURI, ClassResourceInfo cri, boolean isRoot, 
+    public ClientProxyImpl(URI baseURI, ClassResourceInfo cri, boolean isRoot, 
                            boolean inheritHeaders, Object... varValues) {
-        super(baseURI, currentURI);
+        super(baseURI);
+        this.cri = cri;
+        this.isRoot = isRoot;
+        this.inheritHeaders = inheritHeaders;
+        initValuesMap(varValues);
+    }
+    
+    public ClientProxyImpl(ClientState initialState, ClassResourceInfo cri, boolean isRoot, 
+                           boolean inheritHeaders, Object... varValues) {
+        super(initialState);
         this.cri = cri;
         this.isRoot = isRoot;
         this.inheritHeaders = inheritHeaders;
@@ -148,15 +157,16 @@
             if (subCri == null) {
                 reportInvalidResourceMethod(m, "INVALID_SUBRESOURCE");
             }
-            ClientProxyImpl proxyImpl = new ClientProxyImpl(getBaseURI(), uri, subCri, false, inheritHeaders);
-            proxyImpl.setConfiguration(getConfiguration());
             
-            Object proxy = JAXRSClientFactory.create(m.getReturnType(), proxyImpl);
+            MultivaluedMap<String, String> subHeaders = paramHeaders;
             if (inheritHeaders) {
-                WebClient.client(proxy).headers(headers);
+                subHeaders.putAll(headers);    
             }
-            WebClient.client(proxy).headers(paramHeaders);
-            return proxy;
+            
+            ClientState newState = getState().newState(uri, headers);
+            ClientProxyImpl proxyImpl = new ClientProxyImpl(newState, subCri, false, inheritHeaders);
+            proxyImpl.setConfiguration(getConfiguration());
+            return JAXRSClientFactory.create(m.getReturnType(), proxyImpl);
         } 
         
         headers.putAll(paramHeaders);
@@ -443,7 +453,7 @@
     
     protected Object handleResponse(HttpURLConnection connect, Message outMessage, OperationResourceInfo ori) 
         throws Throwable {
-        Response r = setResponseBuilder(connect, outMessage.getExchange()).clone().build();
+        Response r = setResponseBuilder(connect, outMessage.getExchange()).build();
         Method method = ori.getMethodToInvoke();
         checkResponse(method, r, outMessage);
         if (method.getReturnType() == Void.class) { 

Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java?rev=909102&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java Thu Feb 11 18:41:34 2010
@@ -0,0 +1,101 @@
+/**
+ * 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.cxf.jaxrs.client;
+
+import java.net.URI;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+
+/**
+ * Represents the client state :
+ *  - baseURI
+ *  - current uri builder
+ *  - current requestHeaders,
+ *  - current response
+ */
+public interface ClientState {
+    
+    /**
+     * Sets the current builder
+     * @param currentBuilder the builder
+     */
+    void setCurrentBuilder(UriBuilder currentBuilder);
+    
+    /**
+     * Gets the current builder
+     * @return
+     */
+    UriBuilder getCurrentBuilder();
+    
+    /**
+     * Sets the base URI
+     * @param baseURI baseURI
+     */
+    void setBaseURI(URI baseURI);
+    
+    /**
+     * Gets the base URI
+     * @return baseURI
+     */
+    URI getBaseURI();
+    
+    /**
+     * Sets the responseBuilder
+     * @param responseBuilder responseBuilder
+     */
+    void setResponseBuilder(ResponseBuilder responseBuilder);
+    
+    /**
+     * Gets the responseBuilder
+     * @return responseBuilder
+     */
+    ResponseBuilder getResponseBuilder();
+    
+    /**
+     * Sets the request headers
+     * @param requestHeaders request headers
+     */
+    void setRequestHeaders(MultivaluedMap<String, String> requestHeaders);
+    
+    /**
+     * Gets the request headers
+     * @return request headers, may be immutable
+     */
+    MultivaluedMap<String, String> getRequestHeaders();
+    
+    /**
+     * Resets the current state to the baseURI
+     *
+     */
+    void reset();
+    
+    /**
+     * The factory method for creating a new state. 
+     * Example, proxy and WebClient.fromClient will use this method when creating 
+     * subresource proxies and new web clients respectively to ensure thet stay
+     * thread-local if needed
+     * @param baseURI baseURI
+     * @param headers request headers
+     * @return client state
+     */
+    ClientState newState(URI baseURI, MultivaluedMap<String, String> headers);
+}

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java Thu Feb 11 18:41:34 2010
@@ -23,6 +23,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import javax.ws.rs.core.MultivaluedMap;
+
 import org.apache.cxf.common.util.ProxyHelper;
 import org.apache.cxf.jaxrs.model.UserResource;
 
@@ -113,6 +115,23 @@
     }
     
     /**
+     * Creates a thread safe proxy
+     * @param baseAddress baseAddress
+     * @param cls proxy class, if not interface then a CGLIB proxy will be created
+     * @param providers list of providers
+     * @param threadSafe if true then a thread-safe proxy will be created
+     * @return typed proxy
+     */
+    public static <T> T create(String baseAddress, Class<T> cls, List<?> providers, boolean threadSafe) {
+        JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+        bean.setProviders(providers);
+        if (threadSafe) {
+            bean.setInitialState(new ThreadLocalClientState(baseAddress));
+        }
+        return bean.create(cls);
+    }
+    
+    /**
      * Creates a proxy
      * @param baseAddress baseAddress
      * @param cls proxy class, if not interface then a CGLIB proxy will be created
@@ -172,6 +191,26 @@
     }
     
     /**
+     * Creates a thread safe proxy using user resource model
+     * @param baseAddress baseAddress
+     * @param cls proxy class, if not interface then a CGLIB proxy will be created
+     * @param modelRef model location
+     * @param providers list of providers
+     * @param threadSafe if true then thread-safe proxy will be created 
+     * @return typed proxy
+     */
+    public static <T> T createFromModel(String baseAddress, Class<T> cls, String modelRef, 
+                                        List<?> providers, boolean threadSafe) {
+        JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, null);
+        bean.setProviders(providers);
+        bean.setModelRef(modelRef);
+        if (threadSafe) {
+            bean.setInitialState(new ThreadLocalClientState(baseAddress));
+        }
+        return bean.create(cls);
+    }
+    
+    /**
      * Creates a proxy using user resource model
      * @param baseAddress baseAddress
      * @param cls proxy class, if not interface then a CGLIB proxy will be created
@@ -219,10 +258,21 @@
      * @return typed proxy
      */
     public static <T> T fromClient(Client client, Class<T> cls, boolean inheritHeaders) {
+        JAXRSClientFactoryBean bean = getBean(client.getCurrentURI().toString(), cls, null);
+        bean.setInheritHeaders(inheritHeaders);
+        
+        ClientState clientState = WebClient.getClientState(client);
         
-        T proxy = create(client.getCurrentURI(), cls, inheritHeaders);
-        if (inheritHeaders) {
-            WebClient.client(proxy).headers(client.getHeaders());
+        T proxy = null;
+        if (clientState == null) {
+            proxy = bean.create(cls);
+            if (inheritHeaders) {
+                WebClient.client(proxy).headers(client.getHeaders());
+            }
+        } else {
+            MultivaluedMap<String, String> headers = inheritHeaders ? client.getHeaders() : null;
+            bean.setInitialState(clientState.newState(client.getCurrentURI(), headers));
+            proxy = bean.create(cls);
         }
         WebClient.copyProperties(WebClient.client(proxy), client);
         return proxy;

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java Thu Feb 11 18:41:34 2010
@@ -46,6 +46,8 @@
     private String password;
     private boolean inheritHeaders; 
     private MultivaluedMap<String, String> headers;
+    private ClientState initialState;
+    private boolean threadSafe; 
     
     public JAXRSClientFactoryBean() {
         this(new JAXRSServiceFactoryBean());
@@ -57,6 +59,10 @@
         
     }
     
+    public void setThreadSafe(boolean threadSafe) {
+        this.threadSafe = threadSafe;
+    }
+    
     public String getUsername() {
         return username;
     }
@@ -108,8 +114,10 @@
         
         try {
             Endpoint ep = createEndpoint();
-            WebClient client = new WebClient(getAddress());
-            initClient(client, ep);
+            ClientState actualState = getActualState();
+            WebClient client = actualState == null ? new WebClient(getAddress())
+                : new WebClient(actualState);
+            initClient(client, ep, actualState == null);
             
             return client;
         } catch (Exception ex) {
@@ -118,6 +126,18 @@
         }
     }
     
+    private ClientState getActualState() {
+        if (threadSafe) {
+            initialState = new ThreadLocalClientState(getAddress());
+        }
+        if (initialState != null) {
+            return headers != null
+                ? initialState.newState(URI.create(getAddress()), headers) : initialState;
+        } else {
+            return null;
+        }
+    }
+    
     public <T> T create(Class<T> cls, Object... varValues) {
         return cls.cast(createWithValues(varValues));
     }
@@ -131,12 +151,18 @@
         ClassResourceInfo cri = null;
         try {
             Endpoint ep = createEndpoint();
-            URI baseURI = URI.create(getAddress());
             cri = serviceFactory.getClassResourceInfo().get(0);
             boolean isRoot = cri.getURITemplate() != null;
-            ClientProxyImpl proxyImpl = new ClientProxyImpl(baseURI, baseURI, cri, isRoot, inheritHeaders,
-                                                            varValues);
-            initClient(proxyImpl, ep);    
+            ClientProxyImpl proxyImpl = null;
+            ClientState actualState = getActualState();
+            if (actualState == null) {
+                proxyImpl = 
+                    new ClientProxyImpl(URI.create(getAddress()), cri, isRoot, inheritHeaders, varValues);
+            } else {
+                proxyImpl = 
+                    new ClientProxyImpl(actualState, cri, isRoot, inheritHeaders, varValues);
+            }
+            initClient(proxyImpl, ep, actualState == null);    
             
             try {
                 return (Client)ProxyHelper.getProxy(cri.getServiceClass().getClassLoader(),
@@ -174,7 +200,7 @@
         return cs;
     }
     
-    protected void initClient(AbstractClient client, Endpoint ep) {
+    protected void initClient(AbstractClient client, Endpoint ep, boolean addHeaders) {
         
         if (username != null) {
             AuthorizationPolicy authPolicy = new AuthorizationPolicy();
@@ -188,9 +214,11 @@
         client.getConfiguration().setBus(getBus());
         client.getConfiguration().getOutInterceptors().addAll(getOutInterceptors());
         client.getConfiguration().getInInterceptors().addAll(getInInterceptors());
-        if (headers != null) {
+
+        if (headers != null && addHeaders) {
             client.headers(headers);
         }
+        
         setupFactory(ep);
     }
     
@@ -201,4 +229,10 @@
             }
         }
     }
+
+    public void setInitialState(ClientState initialState) {
+        this.initialState = initialState;
+    }
+
+    
 } 

Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java?rev=909102&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java Thu Feb 11 18:41:34 2010
@@ -0,0 +1,104 @@
+/**
+ * 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.cxf.jaxrs.client;
+
+import java.net.URI;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+
+/**
+ * Keeps the client state such as the baseURI, currentURI, requestHeaders, current response
+ *
+ */
+public class LocalClientState implements ClientState {
+    
+    private MultivaluedMap<String, String> requestHeaders = new MetadataMap<String, String>();
+    private ResponseBuilder responseBuilder;
+    private URI baseURI;
+    private UriBuilder currentBuilder;
+    
+    public LocalClientState() {
+        
+    }
+    
+    public LocalClientState(URI baseURI) {
+        this.baseURI = baseURI;
+        this.currentBuilder = UriBuilder.fromUri(baseURI);
+    }
+    
+    public LocalClientState(LocalClientState cs) {
+        this.requestHeaders = new MetadataMap<String, String>(cs.requestHeaders);
+        this.responseBuilder = cs.responseBuilder != null ? cs.responseBuilder.clone() : null;
+        this.baseURI = cs.baseURI;
+        this.currentBuilder = cs.currentBuilder != null ? cs.currentBuilder.clone() : null;
+        
+    }
+    
+    
+    
+    public void setCurrentBuilder(UriBuilder currentBuilder) {
+        this.currentBuilder = currentBuilder;
+    }
+    
+    public UriBuilder getCurrentBuilder() {
+        return currentBuilder;
+    }
+    
+    public void setBaseURI(URI baseURI) {
+        this.baseURI = baseURI;
+    }
+    
+    public URI getBaseURI() {
+        return baseURI;
+    }
+    
+    public void setResponseBuilder(ResponseBuilder responseBuilder) {
+        this.responseBuilder = responseBuilder;
+    }
+    
+    public ResponseBuilder getResponseBuilder() {
+        return responseBuilder;
+    }
+    
+    public void setRequestHeaders(MultivaluedMap<String, String> requestHeaders) {
+        this.requestHeaders = requestHeaders;
+    }
+    
+    public MultivaluedMap<String, String> getRequestHeaders() {
+        return requestHeaders;
+    }
+    
+    public void reset() {
+        requestHeaders.clear();
+        responseBuilder = null;
+        currentBuilder = UriBuilder.fromUri(baseURI);
+    }
+    
+    public ClientState newState(URI newBaseURI, MultivaluedMap<String, String> headers) {
+        ClientState state = new LocalClientState(newBaseURI);
+        if (headers != null) {
+            state.setRequestHeaders(headers);
+        }
+        return state;
+    }
+}

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java?rev=909102&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java Thu Feb 11 18:41:34 2010
@@ -0,0 +1,151 @@
+/**
+ * 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.cxf.jaxrs.client;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * Keeps the client state such as the baseURI, currentURI, requestHeaders, current response
+ * in a thread local storage
+ *
+ */
+public class ThreadLocalClientState implements ClientState {
+    
+    private Map<Thread, LocalClientState> state = 
+        Collections.synchronizedMap(new WeakHashMap<Thread, LocalClientState>());
+    
+    private LocalClientState initialState;
+    
+    private Map<Thread, Long> checkpointMap;
+    private long secondsToKeepState;
+    
+    public ThreadLocalClientState(String baseURI) {
+        this.initialState = new LocalClientState(URI.create(baseURI));
+    }
+    
+    public ThreadLocalClientState(LocalClientState initialState) {
+        this.initialState = initialState;
+    }
+    
+    public void setCurrentBuilder(UriBuilder currentBuilder) {
+        getState().setCurrentBuilder(currentBuilder);
+    }
+    
+    public UriBuilder getCurrentBuilder() {
+        return getState().getCurrentBuilder();
+    }
+    
+    public void setBaseURI(URI baseURI) {
+        getState().setBaseURI(baseURI);
+    }
+    
+    public URI getBaseURI() {
+        return getState().getBaseURI();
+    }
+    
+    public void setResponseBuilder(ResponseBuilder responseBuilder) {
+        getState().setResponseBuilder(responseBuilder);
+    }
+    
+    public ResponseBuilder getResponseBuilder() {
+        return getState().getResponseBuilder();
+    }
+    
+    public void setRequestHeaders(MultivaluedMap<String, String> requestHeaders) {
+        getState().setRequestHeaders(requestHeaders);
+    }
+    
+    public MultivaluedMap<String, String> getRequestHeaders() {
+        return getState().getRequestHeaders();
+    }
+    
+    public void reset() {
+        removeThreadLocalState(Thread.currentThread());
+    }
+    
+    public ClientState newState(URI baseURI, MultivaluedMap<String, String> headers) {
+        LocalClientState ls = new LocalClientState(baseURI);
+        if (headers != null) {
+            ls.setRequestHeaders(headers);
+        }
+        return new ThreadLocalClientState(ls);
+    }
+    
+    private void removeThreadLocalState(Thread t) {
+        state.remove(t);
+        if (checkpointMap != null) {
+            checkpointMap.remove(t);
+        }
+    }
+    
+    protected ClientState getState() {
+        LocalClientState cs = state.get(Thread.currentThread());
+        if (cs == null) {
+            cs = new LocalClientState(initialState);
+            state.put(Thread.currentThread(), cs);
+            if (secondsToKeepState > 0) {
+                long currentTime = System.currentTimeMillis();
+                checkpointMap.put(Thread.currentThread(), currentTime);
+                new CleanupThread(Thread.currentThread(), currentTime).start();
+            }
+        }
+        return cs;
+    }
+
+    public void setSecondsToKeepState(long secondsToKeepState) {
+        this.secondsToKeepState = secondsToKeepState;
+        if (secondsToKeepState > 0) {
+            checkpointMap = new ConcurrentHashMap<Thread, Long>();
+        }
+    }
+
+    private class CleanupThread extends Thread {
+        private Thread thread;
+        private long originalTime;
+        
+        public CleanupThread(Thread thread, long originalTime) {
+            this.thread = thread;
+            this.originalTime = originalTime;
+        }
+        
+        @Override
+        public void run() {
+            try {
+                Thread.sleep(secondsToKeepState);
+                long actualTime = checkpointMap.get(thread);
+                // if times do not match then the original worker thread
+                // has called reset() but came back again to create new local state
+                // hence there's another cleanup thread nearby which will clean the state
+                if (actualTime == originalTime) {
+                    removeThreadLocalState(thread);    
+                }
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+    }
+}

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java Thu Feb 11 18:41:34 2010
@@ -25,6 +25,7 @@
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -41,7 +42,6 @@
 import javax.ws.rs.core.UriBuilder;
 import javax.xml.stream.XMLStreamWriter;
 
-
 import org.apache.cxf.Bus;
 import org.apache.cxf.bus.spring.SpringBusFactory;
 import org.apache.cxf.helpers.CastUtils;
@@ -72,7 +72,11 @@
     }
     
     protected WebClient(URI baseAddress) {
-        super(baseAddress, baseAddress);
+        super(baseAddress);
+    }
+    
+    protected WebClient(ClientState state) {
+        super(state);
     }
     
     /**
@@ -96,6 +100,14 @@
     /**
      * Creates WebClient
      * @param baseURI baseURI
+     */
+    public static WebClient create(String baseURI, boolean threadSafe) {
+        return create(baseURI, Collections.emptyList(), threadSafe);
+    }
+    
+    /**
+     * Creates WebClient
+     * @param baseURI baseURI
      * @param providers list of providers
      */
     public static WebClient create(String baseAddress, List<?> providers) {
@@ -103,6 +115,20 @@
     }
     
     /**
+     * Creates WebClient
+     * @param baseURI baseURI
+     * @param providers list of providers
+     */
+    public static WebClient create(String baseAddress, List<?> providers, boolean threadSafe) {
+        JAXRSClientFactoryBean bean = getBean(baseAddress, null);
+        bean.setProviders(providers);
+        if (threadSafe) {
+            bean.setInitialState(new ThreadLocalClientState(baseAddress));
+        }
+        return bean.createWebClient();        
+    }
+    
+    /**
      * Creates a Spring-configuration aware WebClient
      * @param baseAddress baseAddress
      * @param providers list of providers
@@ -160,9 +186,18 @@
      *        and subresource proxies if any 
      */
     public static WebClient fromClient(Client client, boolean inheritHeaders) {
-        WebClient webClient = create(client.getCurrentURI());
-        if (inheritHeaders) {
-            webClient.headers(client.getHeaders());
+        
+        WebClient webClient = null;
+        
+        ClientState clientState = getClientState(client);
+        if (clientState == null) {
+            webClient = create(client.getCurrentURI());
+            if (inheritHeaders) {
+                webClient.headers(client.getHeaders());
+            }
+        } else {
+            MultivaluedMap<String, String> headers = inheritHeaders ? client.getHeaders() : null;
+            webClient = new WebClient(clientState.newState(client.getCurrentURI(), headers));
         }
         copyProperties(webClient, client);
         return webClient;
@@ -587,7 +622,7 @@
     protected Response handleResponse(HttpURLConnection conn, Message outMessage, 
                                       Class<?> responseClass, Type genericType) {
         try {
-            ResponseBuilder rb = setResponseBuilder(conn, outMessage.getExchange()).clone();
+            ResponseBuilder rb = setResponseBuilder(conn, outMessage.getExchange());
             Response currentResponse = rb.clone().build();
             
             Object entity = readBody(currentResponse, conn, outMessage, responseClass, genericType,
@@ -669,4 +704,15 @@
         bean.setAddress(baseAddress);
         return bean;
     }
+    
+    static ClientState getClientState(Client client) {
+        ClientState clientState = null;
+        if (client instanceof WebClient) { 
+            clientState = ((AbstractClient)client).getState();
+        } else if (client instanceof InvocationHandlerAware) {
+            Object handler = ((InvocationHandlerAware)client).getInvocationHandler();
+            clientState = ((AbstractClient)handler).getState();
+        }
+        return clientState;
+    }
 }

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java Thu Feb 11 18:41:34 2010
@@ -134,6 +134,18 @@
         return value == null ? null : URI.create(value);
     }
     
+    public URI[] getLinks(String expression, Map<String, String> namespaces) {
+        String[] values = getValues(expression, namespaces);
+        if (values == null) {
+            return null;
+        }
+        URI[] uris = new URI[values.length];
+        for (int i = 0; i < values.length; i++) {
+            uris[i] = URI.create(values[i]);
+        }
+        return uris;
+    }
+    
     public URI getBaseURI() {
         Map<String, String> map = new LinkedHashMap<String, String>();
         map.put("xml", XML_NAMESPACE);

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/resources/schemas/jaxrs.xsd
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/resources/schemas/jaxrs.xsd?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/resources/schemas/jaxrs.xsd (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/resources/schemas/jaxrs.xsd Thu Feb 11 18:41:34 2010
@@ -101,6 +101,7 @@
           <xsd:attribute name="username" type="xsd:string"/>
           <xsd:attribute name="password" type="xsd:string"/>
           <xsd:attribute name="serviceName" type="xsd:QName"/>
+          <xsd:attribute name="threadSafe" type="xsd:boolean"/>
         </xsd:extension>
       </xsd:complexContent>
     </xsd:complexType>

Modified: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java?rev=909102&r1=909101&r2=909102&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java (original)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java Thu Feb 11 18:41:34 2010
@@ -633,6 +633,27 @@
         return new Long(theBookId);
     }
     
+    @POST
+    @Path("/booksecho")
+    @Consumes("text/plain")
+    @Produces("text/plain")
+    public Response echoBookNameAndHeader(@HeaderParam("CustomHeader") String headerValue, String name) {
+        return Response.ok().entity(name).header("CustomHeader", headerValue).build();
+    }
+    
+    @Path("/bookstoresub")
+    public BookStore echoThroughBookStoreSub() {
+        return this;
+    }
+    
+    @POST
+    @Path("/booksecho2")
+    @Consumes("text/plain")
+    @Produces("text/plain")
+    public Response echoBookNameAndHeader2(String name) {
+        return echoBookNameAndHeader(httpHeaders.getRequestHeader("CustomHeader").get(0), name);
+    }
+    
     @GET
     @Path("/cd/{CDId}/")
     public CD getCD() {

Added: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java?rev=909102&view=auto
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java (added)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java Thu Feb 11 18:41:34 2010
@@ -0,0 +1,287 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class JAXRSMultithreadedClientTest extends AbstractBusClientServerTestBase {
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        assertTrue("server did not launch correctly",
+                   launchServer(BookServer.class));
+    }
+    
+    @Test
+    public void testStatefulWebClientWithCopy() throws Exception {
+        runWebClients(WebClient.create("http://localhost:9080/bookstore"), 10, false, true);
+    }
+    
+    @Test
+    public void testStatefulWebClientThreadLocal() throws Exception {
+        runWebClients(WebClient.create("http://localhost:9080/bookstore", true), 10, true, true);
+    }
+    
+    @Test
+    public void testStatefulWebClientThreadLocalWithCopy() throws Exception {
+        runWebClients(WebClient.create("http://localhost:9080/bookstore", true), 10, false, true);
+    }
+    
+    @Test
+    public void testSimpleWebClient() throws Exception {
+        WebClient client = WebClient.create("http://localhost:9080/bookstore/booksecho");
+        client.type("text/plain").accept("text/plain").header("CustomHeader", "CustomValue");
+        runWebClients(client, 10, true, false);
+    }
+    
+    @Test
+    public void testSimpleProxy() throws Exception {
+        BookStore proxy = JAXRSClientFactory.create("http://localhost:9080", BookStore.class);
+        runProxies(proxy, 10, true, false);
+    }
+    
+    @Test
+    public void testThreadSafeProxy() throws Exception {
+        BookStore proxy = JAXRSClientFactory.create("http://localhost:9080", BookStore.class,
+                                                    Collections.emptyList(), true);
+        runProxies(proxy, 10, true, true);
+    }
+    
+    @Test
+    public void testThreadSafeProxyWithCopy() throws Exception {
+        BookStore proxy = JAXRSClientFactory.create("http://localhost:9080", BookStore.class,
+                                                    Collections.emptyList(), true);
+        runProxies(proxy, 10, false, true);
+    }
+    
+    @Test
+    public void testThreadSafeSubProxy() throws Exception {
+        BookStore proxy = JAXRSClientFactory.create("http://localhost:9080", BookStore.class,
+                                                    Collections.emptyList(), true);
+        
+        runProxies(proxy.echoThroughBookStoreSub(), 10, true, true);
+    }
+    
+    private void runWebClients(WebClient client, int numberOfClients, 
+        boolean threadSafe, boolean stateCanBeChanged) throws Exception {
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
+                                                             new ArrayBlockingQueue<Runnable>(10));
+        CountDownLatch startSignal = new CountDownLatch(1);
+        CountDownLatch doneSignal = new CountDownLatch(numberOfClients);
+        
+        for (int i = 1; i <= numberOfClients; i++) {
+            WebClient wc = !threadSafe ? WebClient.fromClient(client) : client;
+            String bookName = stateCanBeChanged ? Integer.toString(i) : "TheBook";
+            String bookHeader = stateCanBeChanged ? "value" + i : "CustomValue";
+            
+            executor.execute(new WebClientWorker(wc, bookName, bookHeader, 
+                             startSignal, doneSignal, stateCanBeChanged));
+        }
+        startSignal.countDown();
+        doneSignal.await(60, TimeUnit.SECONDS);
+        executor.shutdownNow();
+        assertEquals("Not all invocations have completed", 0, doneSignal.getCount());
+    }
+    
+    private void runProxies(BookStore proxy, int numberOfClients, 
+                            boolean threadSafe, boolean stateCanBeChanged) throws Exception {
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
+                                                            new ArrayBlockingQueue<Runnable>(10));
+        CountDownLatch startSignal = new CountDownLatch(1);
+        CountDownLatch doneSignal = new CountDownLatch(numberOfClients);
+       
+        for (int i = 1; i <= numberOfClients; i++) {
+            // here we do a double copy : from proxy to web client and back to proxy  
+            BookStore bs = !threadSafe ? JAXRSClientFactory.fromClient(
+                   WebClient.fromClient(WebClient.client(proxy)), BookStore.class) : proxy;
+            String bookName = stateCanBeChanged ? Integer.toString(i) : "TheBook";
+            String bookHeader = stateCanBeChanged ? "value" + i : "CustomValue";
+           
+            executor.execute(new RootProxyWorker(bs, bookName, bookHeader, 
+                            startSignal, doneSignal, stateCanBeChanged));
+        }
+        startSignal.countDown();
+        doneSignal.await(60, TimeUnit.SECONDS);
+        executor.shutdownNow();
+        assertEquals("Not all invocations have completed", 0, doneSignal.getCount());
+    }
+    
+    @Ignore
+    private class WebClientWorker implements Runnable {
+
+        private WebClient client;
+        private String bookName;
+        private String bookHeader;
+        private CountDownLatch startSignal;
+        private CountDownLatch doneSignal;
+        private boolean stateCanBeChanged;
+        
+        public WebClientWorker(WebClient client,
+                               String bookName,
+                               String bookHeader,
+                               CountDownLatch startSignal,
+                               CountDownLatch doneSignal,
+                               boolean stateCanBeChanged) {
+            this.client = client;
+            this.bookName = bookName;
+            this.bookHeader = bookHeader;
+            this.startSignal = startSignal;
+            this.doneSignal = doneSignal;
+            this.stateCanBeChanged = stateCanBeChanged;
+        }
+        
+        public void run() {
+            
+            try {
+                startSignal.await();
+                
+                for (int i = 0; i < 5; i++) {
+                    if (stateCanBeChanged) {
+                        invoke(i);
+                    } else {
+                        doInvoke(bookName, bookHeader);
+                    }
+                }
+                
+                doneSignal.countDown();
+            } catch (InterruptedException ex) {
+                // ignore
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                Assert.fail("WebClientWorker thread failed for " + bookName + "," + bookHeader);
+            } 
+            
+        }
+        
+        private void invoke(int ind) throws Exception {
+            client.type("text/plain").accept("text/plain");
+            
+            String actualHeaderName = bookHeader + ind;
+            String actualBookName = bookName + ind;
+            
+            MultivaluedMap<String, String> map = client.getHeaders();
+            map.putSingle("CustomHeader", actualHeaderName);
+            client.headers(map).path("booksecho");
+                        
+            doInvoke(actualBookName, actualHeaderName);
+            
+            // reset current path
+            client.back(true);
+        }
+        
+        private void doInvoke(String actualBookName, String actualHeaderName) throws Exception {
+            Response response = client.post(actualBookName);
+            
+            assertEquals(actualHeaderName, 
+                response.getMetadata().getFirst("CustomHeader").toString());
+            String responseValue = IOUtils.readStringFromStream((InputStream)response.getEntity());
+            assertEquals(actualBookName, responseValue);
+        }
+    }
+    
+    @Ignore
+    private class RootProxyWorker implements Runnable {
+
+        private BookStore proxy;
+        private String bookName;
+        private String bookHeader;
+        private CountDownLatch startSignal;
+        private CountDownLatch doneSignal;
+        private boolean stateCanBeChanged;
+        
+        public RootProxyWorker(BookStore proxy,
+                               String bookName,
+                               String bookHeader,
+                               CountDownLatch startSignal,
+                               CountDownLatch doneSignal,
+                               boolean stateCanBeChanged) {
+            this.proxy = proxy;
+            this.bookName = bookName;
+            this.bookHeader = bookHeader;
+            this.startSignal = startSignal;
+            this.doneSignal = doneSignal;
+            this.stateCanBeChanged = stateCanBeChanged;
+        }
+        
+        public void run() {
+            
+            try {
+                startSignal.await();
+                
+                for (int i = 0; i < 5; i++) {
+                    invoke(i);
+                }
+                
+                doneSignal.countDown();
+            } catch (InterruptedException ex) {
+                // ignore
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                Assert.fail("WebClientWorker thread failed for " + bookName + "," + bookHeader);
+            } 
+            
+        }
+        
+        private void invoke(int ind) throws Exception {
+            
+            String actualHeaderName = bookHeader + ind;
+            String actualBookName = bookName + ind;
+            
+            if (stateCanBeChanged) {
+                Client c = WebClient.client(proxy);
+                MultivaluedMap<String, String> map = c.getHeaders();
+                map.putSingle("CustomHeader", actualHeaderName);
+                c.headers(map);
+                proxy.echoBookNameAndHeader2(actualBookName);
+                verifyResponse(c.getResponse(), actualBookName, actualHeaderName);
+            } else {
+                verifyResponse(proxy.echoBookNameAndHeader(actualHeaderName, actualBookName),
+                               actualBookName, actualHeaderName);
+            }
+        }
+        
+        private void verifyResponse(Response response, String actualBookName, String actualHeaderName) 
+            throws Exception { 
+            assertEquals(actualHeaderName, 
+                         response.getMetadata().getFirst("CustomHeader").toString());
+            String responseValue = IOUtils.readStringFromStream((InputStream)response.getEntity());
+            assertEquals(actualBookName, responseValue);
+        }
+    }
+}

Propchange: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date