task, Object concurrentResult) throws Exception {
+ TenancyContextHolder.clearContext();
+ }
+
+ private void setTenancyContext(TenancyContext tenancyContext) {
+ this.tenancyContext = tenancyContext;
+ }
+}
diff --git a/src/main/java/org/springframework/tenancy/web/TenancyWebAsyncFilter.java b/src/main/java/org/springframework/tenancy/web/TenancyWebAsyncFilter.java
new file mode 100644
index 0000000..6b05f0d
--- /dev/null
+++ b/src/main/java/org/springframework/tenancy/web/TenancyWebAsyncFilter.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 SpringSource, a division of VMware
+ *
+ * Licensed 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.
+ *
+ * Contributors:
+ * Tasktop Technologies Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.tenancy.web;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.context.request.async.WebAsyncManager;
+import org.springframework.web.context.request.async.WebAsyncUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * This filter provides Asynchronous Request Processing for multi-tenant
+ * application.
+ *
+ * You need to add this filter to your application to support
+ * {@link org.springframework.tenancy.context.TenancyContextHolder} usage
+ * in Spring Web MVC Async applications.
+ *
+ * i.e.
+ *
+ *
+ @Bean
+ @Conditional(TenantCondition.class)
+ public FilterRegistrationBean tenancyWebAsyncFilter() {
+ FilterRegistrationBean registration = new FilterRegistrationBean();
+ TenancyWebAsyncFilter filter = new TenancyWebAsyncFilter();
+ registration.setFilter(filter);
+ registration.setOrder(5); // whatever order
+ return registration;
+ }
+ *
+ *
+ */
+public class TenancyWebAsyncFilter extends OncePerRequestFilter {
+
+ private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
+
+ TenancyWebAsyncFilter tenancyProcessingInterceptor =
+ (TenancyWebAsyncFilter) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
+ if (tenancyProcessingInterceptor == null) {
+ asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, new TenancyContextCallableProcessingInterceptor());
+ }
+
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/springframework/tenancy/web/TenancyContextCallableProcessingInterceptorTest.java b/src/test/java/org/springframework/tenancy/web/TenancyContextCallableProcessingInterceptorTest.java
new file mode 100644
index 0000000..b1ef668
--- /dev/null
+++ b/src/test/java/org/springframework/tenancy/web/TenancyContextCallableProcessingInterceptorTest.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 SpringSource, a division of VMware
+ *
+ * Licensed 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.
+ *
+ * Contributors:
+ * Tasktop Technologies Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.tenancy.web;
+
+import java.util.concurrent.Callable;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.tenancy.context.TenancyContext;
+import org.springframework.tenancy.context.TenancyContextHolder;
+import org.springframework.web.context.request.NativeWebRequest;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TenancyContextCallableProcessingInterceptorTest {
+
+ @Mock
+ private TenancyContext tenancyContext;
+
+ @Mock
+ private Callable> callable;
+
+ @Mock
+ private NativeWebRequest webRequest;
+
+ @After
+ public void clearTenancyContext() {
+ TenancyContextHolder.clearContext();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void constructorNull() {
+ new TenancyContextCallableProcessingInterceptor(null);
+ }
+
+ @Test
+ public void currentTenancyContext() throws Exception {
+ TenancyContextCallableProcessingInterceptor interceptor = new TenancyContextCallableProcessingInterceptor();
+ TenancyContextHolder.setContext(tenancyContext);
+ interceptor.beforeConcurrentHandling(webRequest, callable);
+ TenancyContextHolder.clearContext();
+
+ interceptor.preProcess(webRequest, callable);
+ assertThat(TenancyContextHolder.getContext(), is(tenancyContext));
+
+ interceptor.postProcess(webRequest, callable, null);
+ assertThat(TenancyContextHolder.getContext(), is(not(tenancyContext)));
+ }
+
+ @Test
+ public void specificTenancyContext() throws Exception {
+ TenancyContextCallableProcessingInterceptor interceptor = new TenancyContextCallableProcessingInterceptor(tenancyContext);
+
+ interceptor.preProcess(webRequest, callable);
+ assertThat(TenancyContextHolder.getContext(), is(tenancyContext));
+
+ interceptor.postProcess(webRequest, callable, null);
+ assertThat(TenancyContextHolder.getContext(), is(not(tenancyContext)));
+ }
+}
diff --git a/src/test/java/org/springframework/tenancy/web/TenancyWebAsyncFilterTest.java b/src/test/java/org/springframework/tenancy/web/TenancyWebAsyncFilterTest.java
new file mode 100644
index 0000000..7cb7805
--- /dev/null
+++ b/src/test/java/org/springframework/tenancy/web/TenancyWebAsyncFilterTest.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 SpringSource, a division of VMware
+ *
+ * Licensed 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.
+ *
+ * Contributors:
+ * Tasktop Technologies Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.tenancy.web;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadFactory;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.tenancy.context.TenancyContext;
+import org.springframework.tenancy.context.TenancyContextHolder;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.async.AsyncWebRequest;
+import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
+import org.springframework.web.context.request.async.WebAsyncManager;
+import org.springframework.web.context.request.async.WebAsyncUtils;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TenancyWebAsyncFilterTest {
+ @Mock
+ private TenancyContext tenancyContext;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private AsyncWebRequest asyncWebRequest;
+
+ private WebAsyncManager asyncManager;
+
+ private JoinableThreadFactory threadFactory;
+
+ private MockFilterChain filterChain;
+
+ private TenancyWebAsyncFilter filter;
+
+ @Before
+ public void setUp() {
+ when(asyncWebRequest.getNativeRequest(HttpServletRequest.class)).thenReturn(
+ request);
+ when(request.getRequestURI()).thenReturn("/");
+ filterChain = new MockFilterChain();
+
+ threadFactory = new JoinableThreadFactory();
+ SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
+ executor.setThreadFactory(threadFactory);
+
+ asyncManager = WebAsyncUtils.getAsyncManager(request);
+ asyncManager.setAsyncWebRequest(asyncWebRequest);
+ asyncManager.setTaskExecutor(executor);
+ when(request.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)).thenReturn(
+ asyncManager);
+
+ filter = new TenancyWebAsyncFilter();
+ }
+
+ @After
+ public void clearTenancyContext() {
+ TenancyContextHolder.clearContext();
+ }
+
+ @Test
+ public void doFilterInternalRegistersTenancyContextCallableProcessor()
+ throws Exception {
+ TenancyContextHolder.setContext(tenancyContext);
+ asyncManager
+ .registerCallableInterceptors(new CallableProcessingInterceptorAdapter() {
+ @Override
+ public void postProcess(NativeWebRequest request,
+ Callable task, Object concurrentResult) throws Exception {
+ assertThat(TenancyContextHolder.getContext(), is(not(tenancyContext)));
+ }
+ });
+ filter.doFilterInternal(request, response, filterChain);
+
+ VerifyingCallable verifyingCallable = new VerifyingCallable();
+ asyncManager.startCallableProcessing(verifyingCallable);
+ threadFactory.join();
+ assertThat(asyncManager.getConcurrentResult(), is((Object) tenancyContext));
+ }
+
+ @Test
+ public void doFilterInternalRegistersTenancyContextCallableProcessorContextUpdated()
+ throws Exception {
+ TenancyContextHolder.setContext(TenancyContextHolder.createEmptyContext());
+ asyncManager
+ .registerCallableInterceptors(new CallableProcessingInterceptorAdapter() {
+ @Override
+ public void postProcess(NativeWebRequest request,
+ Callable task, Object concurrentResult) throws Exception {
+ assertThat(TenancyContextHolder.getContext(), is(not(tenancyContext)));
+ }
+ });
+ filter.doFilterInternal(request, response, filterChain);
+ TenancyContextHolder.setContext(tenancyContext);
+
+ VerifyingCallable verifyingCallable = new VerifyingCallable();
+ asyncManager.startCallableProcessing(verifyingCallable);
+ threadFactory.join();
+ assertThat(asyncManager.getConcurrentResult(), is((Object) tenancyContext));
+ }
+
+ private static final class JoinableThreadFactory implements ThreadFactory {
+ private Thread t;
+
+ public Thread newThread(Runnable r) {
+ t = new Thread(r);
+ return t;
+ }
+
+ public void join() throws InterruptedException {
+ t.join();
+ }
+ }
+
+ private class VerifyingCallable implements Callable {
+
+ public TenancyContext call() throws Exception {
+ return TenancyContextHolder.getContext();
+ }
+
+ }
+}