Thread access to @Context objects?

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Thread access to @Context objects?

Chris Hubick
Hi,

I am in the process of porting my Servlet application to Jersey using
it's ServletContainer.  Currently, until I can transition to the
JAX-RS API, @Context HttpServletRequest/Response references are used
for things like "Accept-Language" header access, response URL
encoding, etc.

I have found that if I attempt to access these objects from a spawned
worker thread, it throws java.lang.IllegalStateException inside
com.sun.jersey.server.impl.container.servlet.ThreadLocalInvoker.

I run all Servlet API access through a ServletUtil class which
synchronizes all calls, which works fine directly under Tomcat.  How
can I make this work under Jersey?

Will finishing the port to the JAX-RS (HttpHeaders/UriInfo/etc) API
solve this, or will I have the same problem?

If it's needed (?), I can construct a synchronization utility class
for JAX-RS calls, like I do for the Servlet API, but since not even
that appears to be working, I'm scared I have to somehow funnel all
calls from the worker threads back through the original calling
thread, or replicate all the data I need access to?

Much Thanks!

--
Chris Hubick
mailto:[hidden email]
http://www.hubick.com/chris/

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Paul Sandoz
Administrator
On Oct 23, 2009, at 9:22 PM, Chris Hubick wrote:

> Hi,
>
> I am in the process of porting my Servlet application to Jersey using
> it's ServletContainer.  Currently, until I can transition to the
> JAX-RS API, @Context HttpServletRequest/Response references are used
> for things like "Accept-Language" header access, response URL
> encoding, etc.
>
> I have found that if I attempt to access these objects from a spawned
> worker thread, it throws java.lang.IllegalStateException inside
> com.sun.jersey.server.impl.container.servlet.ThreadLocalInvoker.
>

Thread local proxies are injected so can only be called if:

1) a request is in scope for the invoking thread; and

2) and from methods on the same invoking thread.

I am unsure how to transition or include additional threads that are  
considered within the scope of the request.

You can instead use a Jersey feature and inject:

  @Context ThreadLocal<HttpServletRequest> treq;
  @Context ThreadLocal<HttpServletResponse> tres;

then call the get method to get the direct instance from the resource  
class and that instance can be passed to the worker thread.


> I run all Servlet API access through a ServletUtil class which
> synchronizes all calls, which works fine directly under Tomcat.  How
> can I make this work under Jersey?
>
> Will finishing the port to the JAX-RS (HttpHeaders/UriInfo/etc) API
> solve this, or will I have the same problem?
>

If your resource classes are in the default per-request scope then  
there will be no issue as direct references of the JAX-RS classes you  
mention will be injected.

If you are using non-per-request-scope you can do:

  @Context com.sun.jersey.spi.inject.Injectable<UriInfo> ui;


Hope this helps,
Paul.

> If it's needed (?), I can construct a synchronization utility class
> for JAX-RS calls, like I do for the Servlet API, but since not even
> that appears to be working, I'm scared I have to somehow funnel all
> calls from the worker threads back through the original calling
> thread, or replicate all the data I need access to?
>
> Much Thanks!
>
> --
> Chris Hubick
> mailto:[hidden email]
> http://www.hubick.com/chris/
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Chris Hubick
On Fri, 2009-10-23 at 21:52 +0200, Paul Sandoz wrote:

> On Oct 23, 2009, at 9:22 PM, Chris Hubick wrote:
> > I run all Servlet API access through a ServletUtil class which
> > synchronizes all calls, which works fine directly under Tomcat.  How
> > can I make this work under Jersey?
> >
> > Will finishing the port to the JAX-RS (HttpHeaders/UriInfo/etc) API
> > solve this, or will I have the same problem?
>
> If your resource classes are in the default per-request scope then  
> there will be no issue as direct references of the JAX-RS classes you  
> mention will be injected.

OK, so I finished enough of my conversion to the JAX-RS API to re-enable
threading, and immediately got a ConcurrentModificationException inside
java.util.ArrayList$Itr.next() when multiple threads (all inside the
request scope) try to simultaneously iterate (read-only) through the
List<Locale> returned by HttpHeaders.getAcceptableLanguages().

It appears as though the implementation at
com.sun.jersey.spi.container.ContainerRequest.getAcceptableLanguages()
(as well as other methods in that class) returns the result using a
non-thread-safe ArrayList, rather than say, a thread-safe Vector.

JAX-RS API thread safety would be a huge benefit to me, saving a lot of
ugly synchronization wrapper code around every call, like I needed for
Servlet API access.  Is this a bug?  I don't see anything in the
specification either way?  Does that mean that, to work across JAX-RS
implementations, I am basically forced to synchronize myself? :(

Thanks!

--
Chris Hubick
mailto:[hidden email]
http://www.hubick.com/chris/


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Craig McClanahan-2
On Sun, Nov 1, 2009 at 6:59 PM, Chris Hubick <[hidden email]> wrote:

> On Fri, 2009-10-23 at 21:52 +0200, Paul Sandoz wrote:
>> On Oct 23, 2009, at 9:22 PM, Chris Hubick wrote:
>> > I run all Servlet API access through a ServletUtil class which
>> > synchronizes all calls, which works fine directly under Tomcat.  How
>> > can I make this work under Jersey?
>> >
>> > Will finishing the port to the JAX-RS (HttpHeaders/UriInfo/etc) API
>> > solve this, or will I have the same problem?
>>
>> If your resource classes are in the default per-request scope then
>> there will be no issue as direct references of the JAX-RS classes you
>> mention will be injected.
>
> OK, so I finished enough of my conversion to the JAX-RS API to re-enable
> threading, and immediately got a ConcurrentModificationException inside
> java.util.ArrayList$Itr.next() when multiple threads (all inside the
> request scope) try to simultaneously iterate (read-only) through the
> List<Locale> returned by HttpHeaders.getAcceptableLanguages().
>

If you are accessing servlet request objects from more than one
thread, you're breaking the rules of the Servlet Specification,
whether or not you are using Jersey.  What are you doing that needs to
access this kind of thing from multiple threads?

If there is indeed a valid use case for this, then yes, you are going
to need to take responsibility for synchronizing access yourself.

Craig McClanahan

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Chris Hubick
On Sun, 2009-11-01 at 19:36 -0800, Craig McClanahan wrote:

> On Sun, Nov 1, 2009 at 6:59 PM, Chris Hubick <[hidden email]> wrote:
> > OK, so I finished enough of my conversion to the JAX-RS API to re-enable
> > threading, and immediately got a ConcurrentModificationException inside
> > java.util.ArrayList$Itr.next() when multiple threads (all inside the
> > request scope) try to simultaneously iterate (read-only) through the
> > List<Locale> returned by HttpHeaders.getAcceptableLanguages().
> >
>
> If you are accessing servlet request objects from more than one
> thread, you're breaking the rules of the Servlet Specification,
> whether or not you are using Jersey.

As mentioned in my initial email to this thread, when using the Servlet
API, I perform my own synchronization around any access I make to those
methods.  I am not breaking any rules.

This question is, however, not about the Servlet API, which I mentioned
I am migrating away from.  This question is about the thread safety of
JAX-RS API methods, which is determined by that specification,
regardless of whether it is implemented on top of the Servlet API or
not.

> What are you doing that needs to
> access this kind of thing from multiple threads?

I utilize the "Acceptable Languages" ("Accept-Language" header) to
determine the language of content to return to the client, as designed.

I have multiple threads, each generating separate pieces of that
content, which are later aggregated together.

> If there is indeed a valid use case for this, then yes, you are going
> to need to take responsibility for synchronizing access yourself.

Jersey could choose to offer a thread-safe JAX-RS implementation, even
if not strictly required by the specification.  I am requesting
clarification as to if there is an intention to offer such a thing, as
well as the thread-safety requirements of the JAX-RS specification.

Thanks.

--
Chris Hubick
mailto:[hidden email]
http://www.hubick.com/chris/


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Craig McClanahan-2
On Sun, Nov 1, 2009 at 8:08 PM, Chris Hubick <[hidden email]> wrote:

> On Sun, 2009-11-01 at 19:36 -0800, Craig McClanahan wrote:
>> On Sun, Nov 1, 2009 at 6:59 PM, Chris Hubick <[hidden email]> wrote:
>> > OK, so I finished enough of my conversion to the JAX-RS API to re-enable
>> > threading, and immediately got a ConcurrentModificationException inside
>> > java.util.ArrayList$Itr.next() when multiple threads (all inside the
>> > request scope) try to simultaneously iterate (read-only) through the
>> > List<Locale> returned by HttpHeaders.getAcceptableLanguages().
>> >
>>
>> If you are accessing servlet request objects from more than one
>> thread, you're breaking the rules of the Servlet Specification,
>> whether or not you are using Jersey.
>
> As mentioned in my initial email to this thread, when using the Servlet
> API, I perform my own synchronization around any access I make to those
> methods.  I am not breaking any rules.
>
> This question is, however, not about the Servlet API, which I mentioned
> I am migrating away from.

Well, you're perhaps migrating away from *direct* use of these APIs,
but when you deploy Jersey in a webapp you're still using them
indirectly :-).

>  This question is about the thread safety of
> JAX-RS API methods, which is determined by that specification,
> regardless of whether it is implemented on top of the Servlet API or
> not.
>
>> What are you doing that needs to
>> access this kind of thing from multiple threads?
>
> I utilize the "Acceptable Languages" ("Accept-Language" header) to
> determine the language of content to return to the client, as designed.
>
> I have multiple threads, each generating separate pieces of that
> content, which are later aggregated together.
>
>> If there is indeed a valid use case for this, then yes, you are going
>> to need to take responsibility for synchronizing access yourself.
>
> Jersey could choose to offer a thread-safe JAX-RS implementation, even
> if not strictly required by the specification.  I am requesting
> clarification as to if there is an intention to offer such a thing, as
> well as the thread-safety requirements of the JAX-RS specification.

If you are operating inside a servlet container, you need to obey the
rules of the entire stack.  If you're deploying a standalone Java SE
app, one might argue that these don't apply ... but the concurrency
rules stated in Section 5.1 of the JAX-RS spec still do apply.  In
particular, thread local variables are explicitly endorsed as a
mechanism to pass per-request information without explicitly including
it as method parameters.  Trying to access per-request data, passed in
thread locals, from multiple threads for the same request will fail
because the data will only be visible to the original request
processing thread, even if the passed objects happened to support
concurrent access.

(This, by the way, is the same reason that an EJB is prohibited even
starting additional threads ... the app servers use thread local
variables to manage things like the transaction context.)

The only safe ways to deal with this are either synchronize access to
the underlying data, or (better) pull out everything you need as part
of original processing in the resource method, then pass your own data
objects to the various threads generating the content pieces.  This is
also likely to improve your overall architecture's modularity and
testability, because the content producing logic in each thread would
no longer be tied to either Servlet or JAX-RS APIs directly.

>
> Thanks.
>
> --
> Chris Hubick
> mailto:[hidden email]
> http://www.hubick.com/chris/

Craig

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Chris Hubick
On Sun, 2009-11-01 at 21:09 -0800, Craig McClanahan wrote:

> On Sun, Nov 1, 2009 at 8:08 PM, Chris Hubick <[hidden email]> wrote:
> > Jersey could choose to offer a thread-safe JAX-RS implementation, even
> > if not strictly required by the specification.  I am requesting
> > clarification as to if there is an intention to offer such a thing, as
> > well as the thread-safety requirements of the JAX-RS specification.
>
> The only safe ways to deal with this are either synchronize access to
> the underlying data, or (better) pull out everything you need as part
> of original processing in the resource method, then pass your own data
> objects to the various threads generating the content pieces.  This is
> also likely to improve your overall architecture's modularity and
> testability, because the content producing logic in each thread would
> no longer be tied to either Servlet or JAX-RS APIs directly.

In my opinion, having code and data structures utilizing a broadly
understood and standard API is preferable to a custom API from a
maintenance perspective.  Adding synchronization in the places it's
needed, although ugly, also generally results in less code overall than
writing custom data structures.  More importantly, I can't "pull out"
access to things like SecurityContext.isUserInRole(String).

One of my major draws to Java was it's thread-safe library, it makes me
sad to see this design tenet fall ever further to the wayside.

Anyhow, thanks for your clarifications.

--
Chris Hubick
mailto:[hidden email]
http://www.hubick.com/chris/


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Cowtowncoder
On Sun, Nov 1, 2009 at 10:00 PM, Chris Hubick <[hidden email]> wrote:
...
> In my opinion, having code and data structures utilizing a broadly
> understood and standard API is preferable to a custom API from a
> maintenance perspective.  Adding synchronization in the places it's

Yes, if that API supports such use case. JAX-RS as well as Servlet API
is quite explicitly defined as thread-per-request model. Any
extensions that will utilize additional threads will have to deal with
additional requirements. Not implementations of APIs that are
explicitly not designed to cover such use cases.

> needed, although ugly, also generally results in less code overall than
> writing custom data structures.  More importantly, I can't "pull out"
> access to things like SecurityContext.isUserInRole(String).
>
> One of my major draws to Java was it's thread-safe library, it makes me
> sad to see this design tenet fall ever further to the wayside.

I am not sure I see your point. To me it sounds like you are asking
support for a non-supported use case. For supported use cases,
additional synchronization would be additional overhead without any
benefits; both regarding runtime overhead, and more importantly,
making implementation itself more complicated and more work to
maintain (in addition to initial implementation).
While that would be possible, the question really is: why bother?

-+ Tatu +-

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Paul Sandoz
Administrator
In reply to this post by Chris Hubick

On Nov 2, 2009, at 3:59 AM, Chris Hubick wrote:

> On Fri, 2009-10-23 at 21:52 +0200, Paul Sandoz wrote:
>> On Oct 23, 2009, at 9:22 PM, Chris Hubick wrote:
>>> I run all Servlet API access through a ServletUtil class which
>>> synchronizes all calls, which works fine directly under Tomcat.  How
>>> can I make this work under Jersey?
>>>
>>> Will finishing the port to the JAX-RS (HttpHeaders/UriInfo/etc) API
>>> solve this, or will I have the same problem?
>>
>> If your resource classes are in the default per-request scope then
>> there will be no issue as direct references of the JAX-RS classes you
>> mention will be injected.
>
> OK, so I finished enough of my conversion to the JAX-RS API to re-
> enable
> threading, and immediately got a ConcurrentModificationException  
> inside
> java.util.ArrayList$Itr.next() when multiple threads (all inside the
> request scope) try to simultaneously iterate (read-only) through the
> List<Locale> returned by HttpHeaders.getAcceptableLanguages().
>
> It appears as though the implementation at
> com.sun.jersey.spi.container.ContainerRequest.getAcceptableLanguages()
> (as well as other methods in that class) returns the result using a
> non-thread-safe ArrayList, rather than say, a thread-safe Vector.
>
> JAX-RS API thread safety would be a huge benefit to me, saving a lot  
> of
> ugly synchronization wrapper code around every call, like I needed for
> Servlet API access.  Is this a bug?  I don't see anything in the
> specification either way?  Does that mean that, to work across JAX-RS
> implementations, I am basically forced to synchronize myself? :(
>

Yes. Those classes are not designed to be re-entrant (for the reasons  
that Craig and Tatu give).

IIRC HttpServletRequest is not meant to be re-entrant either. But it  
just so happens you are using something that is, Enumeration (created  
from Vector), because the servlet API is old.

The use of Vector is an implementation detail. In a different servlet  
implementation an implementation of List might be used in conjunction  
with Collections.enumeration. So your previous JAX-RS code may  
potentially break on different Web containers or in future versions of  
the same Web container.

If you want we can work on providing thread-safe adapters for non-
thread-local proxy instances.

public class ReentrantHttpHeaders implements HttpHeaders {
   private final HttpHeaders hhs;

   public ReentrantHttpHeaders(HttpHeaders hhs) {
     this.hhs = hhs;
   }

   public synchronized List<java.util.Locale> getAcceptableLanguages() {
     return Collections.synchronizedList(hhs. getAcceptableLanguages());
   }

   ...

   // Adapting MutilvaluedMap requires a little more work.

}

So you can pass the adapted instance.

Or simple pass:

   Collections.synchronizedList(hhs. getAcceptableLanguages());

to the worker threads if that is sufficient, which as Craig suggests  
may separate out your worker threads from explicit JAX-RS dependencies  
(which should make it easier to test independently).

Paul.

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Chris Hubick
On Mon, 2009-11-02 at 11:08 +0100, Paul Sandoz wrote:
> If you want we can work on providing thread-safe adapters for non-
> thread-local proxy instances.

FYI, I came up with the attached, in case anyone else has a similar
need.

--
Chris Hubick
mailto:[hidden email]
http://www.hubick.com/chris/

import java.net.*;
import java.security.*;
import java.util.*;
import java.util.concurrent.*;

import javax.ws.rs.core.*;


/**
 * JAX-RS Utilities.
 */
public abstract class RESTUtil {

  public static class ConcurrentMultivaluedMapWrapper<K,V> extends AbstractMap<K,List<V>> implements MultivaluedMap<K,V> {
    protected final Map<K,List<V>> multivaluedMap;

    public ConcurrentMultivaluedMapWrapper(final MultivaluedMap<K,V> multivaluedMap) throws IllegalArgumentException {
      if (multivaluedMap == null) throw new IllegalArgumentException("null multivaluedMap");
      this.multivaluedMap = Collections.unmodifiableMap(new ConcurrentHashMap<K,List<V>>(multivaluedMap));
      return;
    }

    @Override
    public Set<Map.Entry<K,List<V>>> entrySet() {
      final Set<Map.Entry<K,List<V>>> entrySet = multivaluedMap.entrySet();
      if (entrySet == null) return null;
      final List<Map.Entry<K,List<V>>> concurrentEntries = new ArrayList<Map.Entry<K,List<V>>>(entrySet.size());
      for (Map.Entry<K,List<V>> entry : entrySet) {
        concurrentEntries.add(new AbstractMap.SimpleEntry<K,List<V>>(entry.getKey(), (entry.getValue() != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<V>(entry.getValue())) : null));
      }
      return Collections.unmodifiableSet(new CopyOnWriteArraySet<Map.Entry<K,List<V>>>(concurrentEntries));
    }

    @Override
    public void clear() {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean containsKey(final Object key) {
      return multivaluedMap.containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
      return multivaluedMap.containsValue(value);
    }

    @Override
    public List<V> get(final Object key) {
      final List<V> value = multivaluedMap.get(key);
      return (value != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<V>(value)) : null;
    }

    @Override
    public boolean isEmpty() {
      return multivaluedMap.isEmpty();
    }

    @Override
    public Set<K> keySet() {
      final Set<K> keySet = multivaluedMap.keySet();
      return (keySet != null) ? Collections.unmodifiableSet(new CopyOnWriteArraySet<K>(keySet)) : null;
    }

    @Override
    public List<V> remove(final Object key) {
      throw new UnsupportedOperationException();
    }

    @Override
    public int size() {
      return multivaluedMap.size();
    }

    @Override
    public void add(final K key, final V value) {
      throw new UnsupportedOperationException();
    }

    @Override
    public V getFirst(final K key) {
      final List<V> values = multivaluedMap.get(key);
      return ((values != null) && (!values.isEmpty())) ? values.get(0) : null;
    }

    @Override
    public void putSingle(final K key, final V value) {
      throw new UnsupportedOperationException();
    }

    @Override
    public List<V> put(final K key, final List<V> value) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(final Map<? extends K,? extends List<V>> m) {
      throw new UnsupportedOperationException();
    }

  } // ConcurrentMultivaluedMapWrapper

  public static class ConcurrentRequestWrapper implements Request {
    protected final Request request;

    public ConcurrentRequestWrapper(final Request request) throws IllegalArgumentException {
      if (request == null) throw new IllegalArgumentException("null request");
      this.request = request;
      return;
    }

    @Override
    public Response.ResponseBuilder evaluatePreconditions() {
      synchronized (request) {
        return request.evaluatePreconditions();
      }
    }

    @Override
    public Response.ResponseBuilder evaluatePreconditions(final EntityTag eTag) {
      synchronized (request) {
        return request.evaluatePreconditions(eTag);
      }
    }

    @Override
    public Response.ResponseBuilder evaluatePreconditions(final Date lastModified) {
      synchronized (request) {
        return request.evaluatePreconditions(lastModified);
      }
    }

    @Override
    public Response.ResponseBuilder evaluatePreconditions(final Date lastModified, final EntityTag eTag) {
      synchronized (request) {
        return request.evaluatePreconditions(lastModified, eTag);
      }
    }

    @Override
    public String getMethod() {
      synchronized (request) {
        return request.getMethod();
      }
    }

    @Override
    public Variant selectVariant(final List<Variant> variants) throws IllegalArgumentException {
      synchronized (request) {
        return request.selectVariant(variants);
      }
    }

  } // ConcurrentRequestWrapper

  public static class ConcurrentUriInfoWrapper implements UriInfo {
    protected final UriInfo uriInfo;

    public ConcurrentUriInfoWrapper(final UriInfo uriInfo) throws IllegalArgumentException {
      if (uriInfo == null) throw new IllegalArgumentException("null uriInfo");
      this.uriInfo = uriInfo;
      return;
    }

    @Override
    public URI getAbsolutePath() {
      synchronized (uriInfo) {
        return uriInfo.getAbsolutePath();
      }
    }

    @Override
    public UriBuilder getAbsolutePathBuilder() {
      synchronized (uriInfo) {
        return uriInfo.getAbsolutePathBuilder();
      }
    }

    @Override
    public URI getBaseUri() {
      synchronized (uriInfo) {
        return uriInfo.getBaseUri();
      }
    }

    @Override
    public UriBuilder getBaseUriBuilder() {
      synchronized (uriInfo) {
        return uriInfo.getBaseUriBuilder();
      }
    }

    @Override
    public List<Object> getMatchedResources() {
      synchronized (uriInfo) {
        final List<Object> matchedResources = uriInfo.getMatchedResources();
        return (matchedResources != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<Object>(matchedResources)) : null;
      }
    }

    @Override
    public List<String> getMatchedURIs() {
      synchronized (uriInfo) {
        final List<String> matchedURIs = uriInfo.getMatchedURIs();
        return (matchedURIs != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<String>(matchedURIs)) : null;
      }
    }

    @Override
    public List<String> getMatchedURIs(boolean decode) {
      synchronized (uriInfo) {
        final List<String> matchedURIs = uriInfo.getMatchedURIs(decode);
        return (matchedURIs != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<String>(matchedURIs)) : null;
      }
    }

    @Override
    public String getPath() {
      synchronized (uriInfo) {
        return uriInfo.getPath();
      }
    }

    @Override
    public String getPath(boolean decode) {
      synchronized (uriInfo) {
        return uriInfo.getPath(decode);
      }
    }

    @Override
    public MultivaluedMap<String,String> getPathParameters() {
      synchronized (uriInfo) {
        final MultivaluedMap<String,String> pathParameters = uriInfo.getPathParameters();
        return (pathParameters != null) ? new ConcurrentMultivaluedMapWrapper<String,String>(pathParameters) : null;
      }
    }

    @Override
    public MultivaluedMap<String,String> getPathParameters(boolean decode) {
      synchronized (uriInfo) {
        final MultivaluedMap<String,String> pathParameters = uriInfo.getPathParameters(decode);
        return (pathParameters != null) ? new ConcurrentMultivaluedMapWrapper<String,String>(pathParameters) : null;
      }
    }

    @Override
    public List<PathSegment> getPathSegments() {
      synchronized (uriInfo) {
        final List<PathSegment> pathSegments = uriInfo.getPathSegments();
        return (pathSegments != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<PathSegment>(pathSegments)) : null;
      }
    }

    @Override
    public List<PathSegment> getPathSegments(boolean decode) {
      synchronized (uriInfo) {
        final List<PathSegment> pathSegments = uriInfo.getPathSegments(decode);
        return (pathSegments != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<PathSegment>(pathSegments)) : null;
      }
    }

    @Override
    public MultivaluedMap<String,String> getQueryParameters() {
      synchronized (uriInfo) {
        final MultivaluedMap<String,String> queryParameters = uriInfo.getQueryParameters();
        return (queryParameters != null) ? new ConcurrentMultivaluedMapWrapper<String,String>(queryParameters) : null;
      }
    }

    @Override
    public MultivaluedMap<String,String> getQueryParameters(boolean decode) {
      synchronized (uriInfo) {
        final MultivaluedMap<String,String> queryParameters = uriInfo.getQueryParameters(decode);
        return (queryParameters != null) ? new ConcurrentMultivaluedMapWrapper<String,String>(queryParameters) : null;
      }
    }

    @Override
    public URI getRequestUri() {
      synchronized (uriInfo) {
        return uriInfo.getRequestUri();
      }
    }

    @Override
    public UriBuilder getRequestUriBuilder() {
      synchronized (uriInfo) {
        return uriInfo.getRequestUriBuilder();
      }
    }

  } // ConcurrentUriInfoWrapper

  public static class ConcurrentSecurityContextWrapper implements SecurityContext {
    protected final SecurityContext securityContext;

    public ConcurrentSecurityContextWrapper(final SecurityContext securityContext) throws IllegalArgumentException {
      if (securityContext == null) throw new IllegalArgumentException("null securityContext");
      this.securityContext = securityContext;
      return;
    }

    @Override
    public String getAuthenticationScheme() {
      synchronized (securityContext) {
        return securityContext.getAuthenticationScheme();
      }
    }

    @Override
    public Principal getUserPrincipal() {
      synchronized (securityContext) {
        return securityContext.getUserPrincipal();
      }
    }

    @Override
    public boolean isSecure() {
      synchronized (securityContext) {
        return securityContext.isSecure();
      }
    }

    @Override
    public boolean isUserInRole(final String role) {
      synchronized (securityContext) {
        return securityContext.isUserInRole(role);
      }
    }

  } // ConcurrentSecurityContextWrapper

  public static class ConcurrentHttpHeadersWrapper implements HttpHeaders {
    protected final HttpHeaders httpHeaders;

    public ConcurrentHttpHeadersWrapper(final HttpHeaders httpHeaders) throws IllegalArgumentException {
      if (httpHeaders == null) throw new IllegalArgumentException("null httpHeaders");
      this.httpHeaders = httpHeaders;
      return;
    }

    @Override
    public List<Locale> getAcceptableLanguages() {
      synchronized (httpHeaders) {
        final List<Locale> acceptableLanguages = httpHeaders.getAcceptableLanguages();
        return (acceptableLanguages != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<Locale>(acceptableLanguages)) : null;
      }
    }

    @Override
    public List<MediaType> getAcceptableMediaTypes() {
      synchronized (httpHeaders) {
        final List<MediaType> acceptableMediaTypes = httpHeaders.getAcceptableMediaTypes();
        return (acceptableMediaTypes != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<MediaType>(acceptableMediaTypes)) : null;
      }
    }

    @Override
    public Map<String,Cookie> getCookies() {
      synchronized (httpHeaders) {
        final Map<String,Cookie> cookies = httpHeaders.getCookies();
        return (cookies != null) ? Collections.unmodifiableMap(new ConcurrentHashMap<String,Cookie>(cookies)) : null;
      }
    }

    @Override
    public Locale getLanguage() {
      synchronized (httpHeaders) {
        return httpHeaders.getLanguage();
      }
    }

    @Override
    public MediaType getMediaType() {
      synchronized (httpHeaders) {
        return httpHeaders.getMediaType();
      }
    }

    @Override
    public List<String> getRequestHeader(final String name) {
      synchronized (httpHeaders) {
        final List<String> requestHeader = httpHeaders.getRequestHeader(name);
        return (requestHeader != null) ? Collections.unmodifiableList(new CopyOnWriteArrayList<String>(requestHeader)) : null;
      }
    }

    @Override
    public MultivaluedMap<String,String> getRequestHeaders() {
      synchronized (httpHeaders) {
        final MultivaluedMap<String,String> requestHeaders = httpHeaders.getRequestHeaders();
        return (requestHeaders != null) ? new ConcurrentMultivaluedMapWrapper<String,String>(requestHeaders) : null;
      }
    }

  } // ConcurrentHttpHeadersWrapper

}


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]
Reply | Threaded
Open this post in threaded view
|

Re: Thread access to @Context objects?

Paul Sandoz
Administrator
On Nov 4, 2009, at 5:09 AM, Chris Hubick wrote:

> On Mon, 2009-11-02 at 11:08 +0100, Paul Sandoz wrote:
>> If you want we can work on providing thread-safe adapters for non-
>> thread-local proxy instances.
>
> FYI, I came up with the attached, in case anyone else has a similar
> need.

Thanks. I logged a feature, pointing to your email.

Paul.

>
> --  
> Chris Hubick
> mailto:[hidden email]
> http://www.hubick.com/chris/
> import java.net.*;
> import java.security.*;
> import java.util.*;
> import java.util.concurrent.*;
>
> import javax.ws.rs.core.*;
>
>
> /**
> * JAX-RS Utilities.
> */
> public abstract class RESTUtil {
>
>  public static class ConcurrentMultivaluedMapWrapper<K,V> extends  
> AbstractMap<K,List<V>> implements MultivaluedMap<K,V> {
>    protected final Map<K,List<V>> multivaluedMap;
>
>    public ConcurrentMultivaluedMapWrapper(final MultivaluedMap<K,V>  
> multivaluedMap) throws IllegalArgumentException {
>      if (multivaluedMap == null) throw new  
> IllegalArgumentException("null multivaluedMap");
>      this.multivaluedMap = Collections.unmodifiableMap(new  
> ConcurrentHashMap<K,List<V>>(multivaluedMap));
>      return;
>    }
>
>    @Override
>    public Set<Map.Entry<K,List<V>>> entrySet() {
>      final Set<Map.Entry<K,List<V>>> entrySet =  
> multivaluedMap.entrySet();
>      if (entrySet == null) return null;
>      final List<Map.Entry<K,List<V>>> concurrentEntries = new  
> ArrayList<Map.Entry<K,List<V>>>(entrySet.size());
>      for (Map.Entry<K,List<V>> entry : entrySet) {
>        concurrentEntries.add(new  
> AbstractMap.SimpleEntry<K,List<V>>(entry.getKey(),  
> (entry.getValue() != null) ? Collections.unmodifiableList(new  
> CopyOnWriteArrayList<V>(entry.getValue())) : null));
>      }
>      return Collections.unmodifiableSet(new  
> CopyOnWriteArraySet<Map.Entry<K,List<V>>>(concurrentEntries));
>    }
>
>    @Override
>    public void clear() {
>      throw new UnsupportedOperationException();
>    }
>
>    @Override
>    public boolean containsKey(final Object key) {
>      return multivaluedMap.containsKey(key);
>    }
>
>    @Override
>    public boolean containsValue(final Object value) {
>      return multivaluedMap.containsValue(value);
>    }
>
>    @Override
>    public List<V> get(final Object key) {
>      final List<V> value = multivaluedMap.get(key);
>      return (value != null) ? Collections.unmodifiableList(new  
> CopyOnWriteArrayList<V>(value)) : null;
>    }
>
>    @Override
>    public boolean isEmpty() {
>      return multivaluedMap.isEmpty();
>    }
>
>    @Override
>    public Set<K> keySet() {
>      final Set<K> keySet = multivaluedMap.keySet();
>      return (keySet != null) ? Collections.unmodifiableSet(new  
> CopyOnWriteArraySet<K>(keySet)) : null;
>    }
>
>    @Override
>    public List<V> remove(final Object key) {
>      throw new UnsupportedOperationException();
>    }
>
>    @Override
>    public int size() {
>      return multivaluedMap.size();
>    }
>
>    @Override
>    public void add(final K key, final V value) {
>      throw new UnsupportedOperationException();
>    }
>
>    @Override
>    public V getFirst(final K key) {
>      final List<V> values = multivaluedMap.get(key);
>      return ((values != null) && (!values.isEmpty())) ?  
> values.get(0) : null;
>    }
>
>    @Override
>    public void putSingle(final K key, final V value) {
>      throw new UnsupportedOperationException();
>    }
>
>    @Override
>    public List<V> put(final K key, final List<V> value) {
>      throw new UnsupportedOperationException();
>    }
>
>    @Override
>    public void putAll(final Map<? extends K,? extends List<V>> m) {
>      throw new UnsupportedOperationException();
>    }
>
>  } // ConcurrentMultivaluedMapWrapper
>
>  public static class ConcurrentRequestWrapper implements Request {
>    protected final Request request;
>
>    public ConcurrentRequestWrapper(final Request request) throws  
> IllegalArgumentException {
>      if (request == null) throw new IllegalArgumentException("null  
> request");
>      this.request = request;
>      return;
>    }
>
>    @Override
>    public Response.ResponseBuilder evaluatePreconditions() {
>      synchronized (request) {
>        return request.evaluatePreconditions();
>      }
>    }
>
>    @Override
>    public Response.ResponseBuilder evaluatePreconditions(final  
> EntityTag eTag) {
>      synchronized (request) {
>        return request.evaluatePreconditions(eTag);
>      }
>    }
>
>    @Override
>    public Response.ResponseBuilder evaluatePreconditions(final Date  
> lastModified) {
>      synchronized (request) {
>        return request.evaluatePreconditions(lastModified);
>      }
>    }
>
>    @Override
>    public Response.ResponseBuilder evaluatePreconditions(final Date  
> lastModified, final EntityTag eTag) {
>      synchronized (request) {
>        return request.evaluatePreconditions(lastModified, eTag);
>      }
>    }
>
>    @Override
>    public String getMethod() {
>      synchronized (request) {
>        return request.getMethod();
>      }
>    }
>
>    @Override
>    public Variant selectVariant(final List<Variant> variants) throws  
> IllegalArgumentException {
>      synchronized (request) {
>        return request.selectVariant(variants);
>      }
>    }
>
>  } // ConcurrentRequestWrapper
>
>  public static class ConcurrentUriInfoWrapper implements UriInfo {
>    protected final UriInfo uriInfo;
>
>    public ConcurrentUriInfoWrapper(final UriInfo uriInfo) throws  
> IllegalArgumentException {
>      if (uriInfo == null) throw new IllegalArgumentException("null  
> uriInfo");
>      this.uriInfo = uriInfo;
>      return;
>    }
>
>    @Override
>    public URI getAbsolutePath() {
>      synchronized (uriInfo) {
>        return uriInfo.getAbsolutePath();
>      }
>    }
>
>    @Override
>    public UriBuilder getAbsolutePathBuilder() {
>      synchronized (uriInfo) {
>        return uriInfo.getAbsolutePathBuilder();
>      }
>    }
>
>    @Override
>    public URI getBaseUri() {
>      synchronized (uriInfo) {
>        return uriInfo.getBaseUri();
>      }
>    }
>
>    @Override
>    public UriBuilder getBaseUriBuilder() {
>      synchronized (uriInfo) {
>        return uriInfo.getBaseUriBuilder();
>      }
>    }
>
>    @Override
>    public List<Object> getMatchedResources() {
>      synchronized (uriInfo) {
>        final List<Object> matchedResources =  
> uriInfo.getMatchedResources();
>        return (matchedResources != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<Object>(matchedResources)) : null;
>      }
>    }
>
>    @Override
>    public List<String> getMatchedURIs() {
>      synchronized (uriInfo) {
>        final List<String> matchedURIs = uriInfo.getMatchedURIs();
>        return (matchedURIs != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<String>(matchedURIs)) : null;
>      }
>    }
>
>    @Override
>    public List<String> getMatchedURIs(boolean decode) {
>      synchronized (uriInfo) {
>        final List<String> matchedURIs =  
> uriInfo.getMatchedURIs(decode);
>        return (matchedURIs != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<String>(matchedURIs)) : null;
>      }
>    }
>
>    @Override
>    public String getPath() {
>      synchronized (uriInfo) {
>        return uriInfo.getPath();
>      }
>    }
>
>    @Override
>    public String getPath(boolean decode) {
>      synchronized (uriInfo) {
>        return uriInfo.getPath(decode);
>      }
>    }
>
>    @Override
>    public MultivaluedMap<String,String> getPathParameters() {
>      synchronized (uriInfo) {
>        final MultivaluedMap<String,String> pathParameters =  
> uriInfo.getPathParameters();
>        return (pathParameters != null) ? new  
> ConcurrentMultivaluedMapWrapper<String,String>(pathParameters) : null;
>      }
>    }
>
>    @Override
>    public MultivaluedMap<String,String> getPathParameters(boolean  
> decode) {
>      synchronized (uriInfo) {
>        final MultivaluedMap<String,String> pathParameters =  
> uriInfo.getPathParameters(decode);
>        return (pathParameters != null) ? new  
> ConcurrentMultivaluedMapWrapper<String,String>(pathParameters) : null;
>      }
>    }
>
>    @Override
>    public List<PathSegment> getPathSegments() {
>      synchronized (uriInfo) {
>        final List<PathSegment> pathSegments =  
> uriInfo.getPathSegments();
>        return (pathSegments != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<PathSegment>(pathSegments)) : null;
>      }
>    }
>
>    @Override
>    public List<PathSegment> getPathSegments(boolean decode) {
>      synchronized (uriInfo) {
>        final List<PathSegment> pathSegments =  
> uriInfo.getPathSegments(decode);
>        return (pathSegments != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<PathSegment>(pathSegments)) : null;
>      }
>    }
>
>    @Override
>    public MultivaluedMap<String,String> getQueryParameters() {
>      synchronized (uriInfo) {
>        final MultivaluedMap<String,String> queryParameters =  
> uriInfo.getQueryParameters();
>        return (queryParameters != null) ? new  
> ConcurrentMultivaluedMapWrapper<String,String>(queryParameters) :  
> null;
>      }
>    }
>
>    @Override
>    public MultivaluedMap<String,String> getQueryParameters(boolean  
> decode) {
>      synchronized (uriInfo) {
>        final MultivaluedMap<String,String> queryParameters =  
> uriInfo.getQueryParameters(decode);
>        return (queryParameters != null) ? new  
> ConcurrentMultivaluedMapWrapper<String,String>(queryParameters) :  
> null;
>      }
>    }
>
>    @Override
>    public URI getRequestUri() {
>      synchronized (uriInfo) {
>        return uriInfo.getRequestUri();
>      }
>    }
>
>    @Override
>    public UriBuilder getRequestUriBuilder() {
>      synchronized (uriInfo) {
>        return uriInfo.getRequestUriBuilder();
>      }
>    }
>
>  } // ConcurrentUriInfoWrapper
>
>  public static class ConcurrentSecurityContextWrapper implements  
> SecurityContext {
>    protected final SecurityContext securityContext;
>
>    public ConcurrentSecurityContextWrapper(final SecurityContext  
> securityContext) throws IllegalArgumentException {
>      if (securityContext == null) throw new  
> IllegalArgumentException("null securityContext");
>      this.securityContext = securityContext;
>      return;
>    }
>
>    @Override
>    public String getAuthenticationScheme() {
>      synchronized (securityContext) {
>        return securityContext.getAuthenticationScheme();
>      }
>    }
>
>    @Override
>    public Principal getUserPrincipal() {
>      synchronized (securityContext) {
>        return securityContext.getUserPrincipal();
>      }
>    }
>
>    @Override
>    public boolean isSecure() {
>      synchronized (securityContext) {
>        return securityContext.isSecure();
>      }
>    }
>
>    @Override
>    public boolean isUserInRole(final String role) {
>      synchronized (securityContext) {
>        return securityContext.isUserInRole(role);
>      }
>    }
>
>  } // ConcurrentSecurityContextWrapper
>
>  public static class ConcurrentHttpHeadersWrapper implements  
> HttpHeaders {
>    protected final HttpHeaders httpHeaders;
>
>    public ConcurrentHttpHeadersWrapper(final HttpHeaders  
> httpHeaders) throws IllegalArgumentException {
>      if (httpHeaders == null) throw new  
> IllegalArgumentException("null httpHeaders");
>      this.httpHeaders = httpHeaders;
>      return;
>    }
>
>    @Override
>    public List<Locale> getAcceptableLanguages() {
>      synchronized (httpHeaders) {
>        final List<Locale> acceptableLanguages =  
> httpHeaders.getAcceptableLanguages();
>        return (acceptableLanguages != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<Locale>(acceptableLanguages)) : null;
>      }
>    }
>
>    @Override
>    public List<MediaType> getAcceptableMediaTypes() {
>      synchronized (httpHeaders) {
>        final List<MediaType> acceptableMediaTypes =  
> httpHeaders.getAcceptableMediaTypes();
>        return (acceptableMediaTypes != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<MediaType>(acceptableMediaTypes)) : null;
>      }
>    }
>
>    @Override
>    public Map<String,Cookie> getCookies() {
>      synchronized (httpHeaders) {
>        final Map<String,Cookie> cookies = httpHeaders.getCookies();
>        return (cookies != null) ? Collections.unmodifiableMap(new  
> ConcurrentHashMap<String,Cookie>(cookies)) : null;
>      }
>    }
>
>    @Override
>    public Locale getLanguage() {
>      synchronized (httpHeaders) {
>        return httpHeaders.getLanguage();
>      }
>    }
>
>    @Override
>    public MediaType getMediaType() {
>      synchronized (httpHeaders) {
>        return httpHeaders.getMediaType();
>      }
>    }
>
>    @Override
>    public List<String> getRequestHeader(final String name) {
>      synchronized (httpHeaders) {
>        final List<String> requestHeader =  
> httpHeaders.getRequestHeader(name);
>        return (requestHeader != null) ?  
> Collections.unmodifiableList(new  
> CopyOnWriteArrayList<String>(requestHeader)) : null;
>      }
>    }
>
>    @Override
>    public MultivaluedMap<String,String> getRequestHeaders() {
>      synchronized (httpHeaders) {
>        final MultivaluedMap<String,String> requestHeaders =  
> httpHeaders.getRequestHeaders();
>        return (requestHeaders != null) ? new  
> ConcurrentMultivaluedMapWrapper<String,String>(requestHeaders) : null;
>      }
>    }
>
>  } // ConcurrentHttpHeadersWrapper
>
> }
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]