callable) {
+ return wrap(callable, transferRequest());
+ }
+
+ /**
+ * Returns an object that "transfers" the request to another thread. This acts
+ * as a way of transporting request context data from the current thread to a
+ * future thread. The transferred scope is the one active for the thread that
+ * calls this method. A later call to {@code open()} activates the transferred
+ * the scope, including propagating any objects scoped at that time.
+ *
+ * As opposed to {@link #continueRequest}, this method propagates all
+ * existing scoped objects. The primary use case is in server implementations
+ * where you can detach the request processing thread while waiting for data,
+ * and reattach to a different thread to finish processing at a later time.
+ *
+ *
Because request-scoped objects are not typically thread-safe, it is
+ * important to avoid applying the same request scope concurrently. The
+ * returned Scoper will block on open until the current thread has released
+ * the request scope.
+ *
+ * @return an object that when opened will initiate the request scope
+ * @throws OutOfScopeException if this method is called from a non-request
+ * thread, or if the request has completed.
+ * @since 5.0
+ */
+ public static RequestScoper transferRequest() {
return (GuiceFilter.localContext.get() != null)
- ? transferHttpRequest(callable)
- : transferNonHttpRequest(callable);
+ ? transferHttpRequest()
+ : transferNonHttpRequest();
}
- private static Callable transferHttpRequest(final Callable callable) {
+ private static RequestScoper transferHttpRequest() {
final GuiceFilter.Context context = GuiceFilter.localContext.get();
if (context == null) {
throw new OutOfScopeException("Not in a request scope");
}
- return new Callable() {
- public T call() throws Exception {
- return context.call(callable);
- }
- };
+ return context;
}
- private static Callable transferNonHttpRequest(final Callable callable) {
+ private static RequestScoper transferNonHttpRequest() {
final Context context = requestScopeContext.get();
if (context == null) {
throw new OutOfScopeException("Not in a request scope");
}
- return new Callable() {
- public T call() throws Exception {
- return context.call(callable);
- }
- };
+ return context;
}
/**
@@ -328,8 +353,28 @@ public static boolean isRequestScoped(Binding> binding) {
* that exposes the instances in the {@code seedMap} as scoped keys.
* @since 3.0
*/
- public static Callable scopeRequest(final Callable callable,
+ public static Callable scopeRequest(Callable callable,
Map, Object> seedMap) {
+ return wrap(callable, scopeRequest(seedMap));
+ }
+
+ /**
+ * Returns an object that will apply request scope to a block of code. This is
+ * not the same as the HTTP request scope, but is used if no HTTP request
+ * scope is in progress. In this way, keys can be scoped as @RequestScoped and
+ * exist in non-HTTP requests (for example: RPC requests) as well as in HTTP
+ * request threads.
+ *
+ * The returned object will throw a {@link ScopingException} when opened
+ * if there is a request scope already active on the current thread.
+ *
+ * @param seedMap the initial set of scoped instances for Guice to seed the
+ * request scope with. To seed a key with null, use {@code null} as
+ * the value.
+ * @return an object that when opened will initiate the request scope
+ * @since 5.0
+ */
+ public static RequestScoper scopeRequest(Map, Object> seedMap) {
Preconditions.checkArgument(null != seedMap,
"Seed map cannot be null, try passing in Collections.emptyMap() instead.");
@@ -342,14 +387,13 @@ public static Callable scopeRequest(final Callable callable,
}
});
context.map.putAll(validatedAndCanonicalizedMap);
-
- return new Callable() {
- public T call() throws Exception {
+ return new RequestScoper() {
+ @Override public CloseableScope open() {
checkScopingState(null == GuiceFilter.localContext.get(),
"An HTTP request is already in progress, cannot scope a new request in this thread.");
checkScopingState(null == requestScopeContext.get(),
"A request scope is already in progress, cannot scope a new request in this thread.");
- return context.call(callable);
+ return context.open();
}
};
}
@@ -371,19 +415,23 @@ private static Object validateAndCanonicalizeValue(Key> key, Object object) {
return object;
}
- private static class Context {
+ private static class Context implements RequestScoper {
final Map map = Maps.newHashMap();
// Synchronized to prevent two threads from using the same request
// scope concurrently.
- synchronized T call(Callable callable) throws Exception {
- Context previous = requestScopeContext.get();
+ final Lock lock = new ReentrantLock();
+
+ @Override public CloseableScope open() {
+ lock.lock();
+ final Context previous = requestScopeContext.get();
requestScopeContext.set(this);
- try {
- return callable.call();
- } finally {
- requestScopeContext.set(previous);
- }
+ return new CloseableScope() {
+ @Override public void close() {
+ requestScopeContext.set(previous);
+ lock.unlock();
+ }
+ };
}
}
@@ -392,4 +440,19 @@ private static void checkScopingState(boolean condition, String msg) {
throw new ScopingException(msg);
}
}
+
+ private static final Callable wrap(
+ final Callable delegate, final RequestScoper requestScoper) {
+ return new Callable() {
+ public T call() throws Exception {
+ RequestScoper.CloseableScope scope = requestScoper.open();
+ try {
+ return delegate.call();
+ } finally {
+ scope.close();
+ }
+ }
+ };
+ }
+
}
diff --git a/extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java b/extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java
index dbd18f774c..edd83fbc33 100644
--- a/extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java
+++ b/extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java
@@ -59,6 +59,13 @@ public void testTransferNonHttp_outOfScope() {
} catch (OutOfScopeException expected) {}
}
+ public void testTransferNonHttp_outOfScope_closeable() {
+ try {
+ ServletScopes.transferRequest();
+ fail();
+ } catch (OutOfScopeException expected) {}
+ }
+
public void testTransferNonHttpRequest() throws Exception {
final Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
@@ -89,6 +96,44 @@ public void testTransferNonHttpRequest() throws Exception {
executor.shutdownNow();
}
+ public void testTransferNonHttpRequest_closeable() throws Exception {
+ final Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bindScope(RequestScoped.class, ServletScopes.REQUEST);
+ }
+
+ @Provides @RequestScoped Object provideObject() {
+ return new Object();
+ }
+ });
+
+ class Data {
+ Object object;
+ RequestScoper scoper;
+ }
+
+ Callable callable = new Callable() {
+ @Override public Data call() {
+ Data data = new Data();
+ data.object = injector.getInstance(Object.class);
+ data.scoper = ServletScopes.transferRequest();
+ return data;
+ }
+ };
+
+ ImmutableMap, Object> seedMap = ImmutableMap.of();
+ Data data = ServletScopes.scopeRequest(callable, seedMap).call();
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ RequestScoper.CloseableScope scope = data.scoper.open();
+ try {
+ assertSame(data.object, injector.getInstance(Object.class));
+ } finally {
+ scope.close();
+ executor.shutdownNow();
+ }
+ }
+
public void testTransferNonHttpRequest_concurrentUseBlocks() throws Exception {
Callable callable = new Callable() {
@Override public Boolean call() throws Exception {
@@ -110,6 +155,37 @@ public void testTransferNonHttpRequest_concurrentUseBlocks() throws Exception {
assertTrue(ServletScopes.scopeRequest(callable, seedMap).call());
}
+ public void testTransferNonHttpRequest_concurrentUseBlocks_closeable() throws Exception {
+ Callable callable = new Callable() {
+ @Override public Boolean call() throws Exception {
+ final RequestScoper scoper = ServletScopes.transferRequest();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ Future future = executor.submit(new Callable() {
+ @Override public Boolean call() {
+ RequestScoper.CloseableScope scope = scoper.open();
+ try {
+ return false;
+ } finally {
+ scope.close();
+ }
+ }
+ });
+ try {
+ return future.get(100, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ return true;
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+ };
+
+ ImmutableMap, Object> seedMap = ImmutableMap.of();
+ assertTrue(ServletScopes.scopeRequest(callable, seedMap).call());
+ }
+
public void testTransferNonHttpRequest_concurrentUseSameThreadOk() throws Exception {
Callable callable = new Callable() {
@Override public Boolean call() throws Exception {
@@ -120,4 +196,20 @@ public void testTransferNonHttpRequest_concurrentUseSameThreadOk() throws Except
ImmutableMap, Object> seedMap = ImmutableMap.of();
assertFalse(ServletScopes.scopeRequest(callable, seedMap).call());
}
+
+ public void testTransferNonHttpRequest_concurrentUseSameThreadOk_closeable() throws Exception {
+ Callable callable = new Callable() {
+ @Override public Boolean call() throws Exception {
+ RequestScoper.CloseableScope scope = ServletScopes.transferRequest().open();
+ try {
+ return false;
+ } finally {
+ scope.close();
+ }
+ }
+ };
+
+ ImmutableMap, Object> seedMap = ImmutableMap.of();
+ assertFalse(ServletScopes.scopeRequest(callable, seedMap).call());
+ }
}
diff --git a/lib/build/cglib-3.1.jar b/lib/build/cglib-3.1.jar
deleted file mode 100644
index 25a5df155a..0000000000
Binary files a/lib/build/cglib-3.1.jar and /dev/null differ
diff --git a/lib/build/cglib-3.2.jar b/lib/build/cglib-3.2.jar
new file mode 100644
index 0000000000..1ea2f0064c
Binary files /dev/null and b/lib/build/cglib-3.2.jar differ
diff --git a/pom.xml b/pom.xml
index 5c59280bdc..5680ac8d4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -159,7 +159,7 @@ See the Apache License Version 2.0 for the specific language governing permissio
cglib
cglib
- 3.1
+ 3.2.0