/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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.
*/
package com.alibaba.citrus.async;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.citrus.async.pipeline.valve.PerformRunnableAsyncValve;
import com.alibaba.citrus.async.support.FakeAsyncExecutor;
import com.alibaba.citrus.async.support.GetScreenResult;
import com.alibaba.citrus.async.support.SetScreenResult;
import com.alibaba.citrus.service.pipeline.impl.PipelineImpl;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.RequestContextChainingService;
import com.alibaba.citrus.util.internal.InterfaceImplementorBuilder;
import org.easymock.Capture;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@RunWith(Parameterized.class)
public class PerformRunnableAsyncValveTests extends AbstractAsyncTests {
private FakeAsyncExecutor executor1;
private PerformRunnableAsyncValve valve;
private PipelineImpl pipeline;
private RequestContextChainingService rccs;
private HttpServletRequest requestMock;
private HttpServletRequest request;
private HttpServletResponse response;
private ServletContext servletContext;
private RequestContext requestContext;
private AsyncContext asyncContext;
private Map<String, Object> requestAttrs = createHashMap();
private boolean doExecuteTask;
public PerformRunnableAsyncValveTests(boolean doExecuteTask) {
this.doExecuteTask = doExecuteTask;
}
@Parameters
public static List<Object[]> data() {
return Arrays.asList(new Object[][] { { true }, { false } });
}
@BeforeClass
public static void initClass() {
defaultFactory = createApplicationContext("performRunnableAsyncValve.xml");
}
@Before
public void init() {
executor1 = (FakeAsyncExecutor) factory.getBean("executor1");
pipeline = (PipelineImpl) factory.getBean("pipeline1");
valve = getValve("pipeline1", 1, PerformRunnableAsyncValve.class);
rccs = (RequestContextChainingService) factory.getBean("requestContexts");
assertNotNull(executor1);
assertNotNull(pipeline);
assertNotNull(valve);
assertNotNull(rccs);
requestMock = createMock(HttpServletRequest.class);
response = createMock(HttpServletResponse.class);
servletContext = createMock(ServletContext.class);
asyncContext = createMock(AsyncContext.class);
expect(requestMock.getDispatcherType()).andReturn(DispatcherType.ASYNC).anyTimes();
expect(requestMock.getLocale()).andReturn(Locale.CHINA).anyTimes();
expect(requestMock.getSession(false)).andReturn(null).anyTimes();
// 以下是为setLoggingContext准备的参数
expect(requestMock.getMethod()).andReturn("GET").once();
expect(requestMock.getRequestURI()).andReturn("http://localhost:8080/test").once();
expect(requestMock.getRequestURL()).andReturn(new StringBuffer("http://localhost:8080/test")).once();
expect(requestMock.getQueryString()).andReturn("x=1").once();
expect(requestMock.getRemoteHost()).andReturn("localhost").once();
expect(requestMock.getRemoteAddr()).andReturn("127.0.0.1").once();
expect(requestMock.getHeader("User-Agent")).andReturn("Safari").once();
expect(requestMock.getHeader("Referer")).andReturn("http://localhost:8080/").once();
expect(requestMock.getCookies()).andReturn(new Cookie[0]).once();
replay(requestMock, response, servletContext);
request = (HttpServletRequest) new InterfaceImplementorBuilder().addInterface(HttpServletRequest.class).toObject(new Object() {
public Object getAttribute(String name) {
return requestAttrs.get(name);
}
public void setAttribute(String name, Object o) {
requestAttrs.put(name, o);
}
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException {
return asyncContext;
}
}, requestMock);
requestContext = rccs.getRequestContext(servletContext, request, response);
rccs.bind(request);
}
@After
public void destroy() {
rccs.unbind(request);
requestAttrs.clear();
}
@Test
public void invoke_resultIsNotRunnable() {
SetScreenResult.set("notARunnable");
pipeline.newInvocation().invoke();
}
private boolean runnableCalled;
@Test
public void invoke_resultIsRunnable() throws Exception {
invokeWithResult(new Runnable() {
public void run() {
runnableCalled = true;
}
}, null, 0);
}
@Test
public void invoke_resultIsCallable() throws Exception {
invokeWithResult(new Callable<Object>() {
public Object call() {
runnableCalled = true;
return "myResultObject";
}
}, "myResultObject", 0);
}
@Test
public void invoke_resultIsCallable_withTimeout() throws Exception {
class MyCallable implements Callable<Object>, AsyncCallback {
public long getTimeout() {
return 1000L;
}
public long getCancelingTimeout() {
return 500L;
}
public Object call() {
runnableCalled = true;
return "myResultObject";
}
}
invokeWithResult(new MyCallable(), "myResultObject", 1000L);
}
@Test
public void invoke_resultIsCallable_withDefaultTimeout() throws Exception {
class MyCallable implements Callable<Object>, AsyncCallback {
public long getTimeout() {
return -1L; // 使用默认值
}
public long getCancelingTimeout() {
return -1L; // 使用默认值
}
public Object call() {
runnableCalled = true;
return "myResultObject";
}
}
invokeWithResult(new MyCallable(), "myResultObject", 0L);
}
private void invokeWithResult(Object result, Object newResult, long timeout) throws Exception {
SetScreenResult.set(result);
asyncContext.setTimeout(timeout);
expectLastCall().once();
Capture<AsyncListener> listenerCap = new Capture<AsyncListener>();
asyncContext.addListener(capture(listenerCap));
expectLastCall().once();
asyncContext.complete(); // 当runnable被执行完时,asyncContext.complete会被调用。
expectLastCall().andThrow(new IllegalStateException()).once(); // 即使complete抛出异常也没有关系。
replay(asyncContext);
pipeline.newInvocation().invoke();
AsyncListener listener = listenerCap.getValue(); // addListener被调用
Callable<?> callable = executor1.getCallable(); // executor.submit(callable)被调用
assertNotNull(callable);
// doExecuteTask控制是否执行runnable
if (doExecuteTask) {
// 在另一个线程中执行runnable,确保request被绑定到线程中(否则RequestProxyTester会报错)
runnableCalled = false;
assertNull(new SimpleAsyncTaskExecutor().submit(callable).get()); // callable总是返回null
assertTrue(runnableCalled); // runnable或callable被执行
assertEquals(newResult, GetScreenResult.get()); // doPerformRunnable以后,值被保存到result中
verify(requestMock, asyncContext);
}
// onTimeout
AsyncEvent event = createMock(AsyncEvent.class);
reset(asyncContext);
// 如果runnable已经被执行,那么event.getAsyncContext().complete()将不被执行。
if (!doExecuteTask) {
expect(event.getAsyncContext()).andReturn(asyncContext).once();
asyncContext.complete();
expectLastCall().andThrow(new IllegalStateException()).once(); // 即使complete抛出异常也没有关系。
}
replay(event, asyncContext);
listener.onTimeout(event); // 当timeout时,asyncContext.complete被调用
verify(event, asyncContext);
}
}