/*
* 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.wicket.request.cycle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.apache.wicket.Application;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.mock.MockWebRequest;
import org.apache.wicket.protocol.http.mock.MockServletContext;
import org.apache.wicket.request.IExceptionMapper;
import org.apache.wicket.request.IRequestCycle;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.RequestHandlerExecutor.ReplaceHandlerException;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.Url;
import org.apache.wicket.resource.DummyApplication;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author Jeremy Thomerson
*/
public class RequestCycleListenerTest extends RequestHandlerExecutorTest
{
private IRequestHandler handler;
private int errorCode;
private int responses;
private int detaches;
private int exceptionsMapped;
/** */
@Before
public void setUp()
{
DummyApplication application = new DummyApplication();
application.setName("dummyTestApplication");
ThreadContext.setApplication(application);
application.setServletContext(new MockServletContext(application, "/"));
application.initApplication();
errorCode = 0;
}
/** */
@After
public void tearDown()
{
ThreadContext.getApplication().internalDestroy();
ThreadContext.detach();
}
private RequestCycle newRequestCycle(final RuntimeException exception)
{
final Response originalResponse = newResponse();
Request request = new MockWebRequest(Url.parse("http://wicket.apache.org"));
handler = new IRequestHandler()
{
@Override
public void respond(IRequestCycle requestCycle)
{
if (exception != null)
{
throw exception;
}
responses++;
}
@Override
public void detach(IRequestCycle requestCycle)
{
detaches++;
}
};
IRequestMapper requestMapper = new IRequestMapper()
{
@Override
public IRequestHandler mapRequest(Request request)
{
return handler;
}
@Override
public Url mapHandler(IRequestHandler requestHandler)
{
throw new UnsupportedOperationException();
}
@Override
public int getCompatibilityScore(Request request)
{
throw new UnsupportedOperationException();
}
};
IExceptionMapper exceptionMapper = new IExceptionMapper()
{
@Override
public IRequestHandler map(Exception e)
{
exceptionsMapped++;
return null;
}
};
RequestCycleContext context = new RequestCycleContext(request, originalResponse,
requestMapper, exceptionMapper);
RequestCycle cycle = new RequestCycle(context);
if (Application.exists())
{
cycle.getListeners().add(Application.get().getRequestCycleListeners());
}
return cycle;
}
/**
* @throws Exception
*/
@Test
public void basicOperations() throws Exception
{
IncrementingListener incrementingListener = new IncrementingListener();
Application.get().getRequestCycleListeners().add(incrementingListener);
RequestCycle cycle = newRequestCycle((RuntimeException)null);
incrementingListener.assertValues(0, 0, 0, 0, 0, 0);
assertValues(0, 0, 0);
cycle.processRequestAndDetach();
// 0 exceptions mapped
incrementingListener.assertValues(1, 1, 1, 1, 0, 1);
// 0 exceptions mapped
assertValues(0, 1, 1);
// TEST WITH TWO LISTENERS
cycle = newRequestCycle((RuntimeException)null);
cycle.getListeners().add(incrementingListener);
cycle.processRequestAndDetach();
// 0 exceptions mapped, all other 2 due to two listeners
incrementingListener.assertValues(2, 2, 2, 2, 0, 2);
// 0 exceptions mapped
assertValues(0, 1, 1);
// TEST WITH TWO LISTENERS AND AN EXCEPTION DURING RESPONSE
cycle = newRequestCycle(new RuntimeException("testing purposes only"));
cycle.getListeners().add(incrementingListener);
cycle.processRequestAndDetach();
// 0 executed
incrementingListener.assertValues(2, 2, 2, 0, 2, 2);
// 1 exception mapped, 0 responded
assertValues(1, 0, 1);
// TEST A REPLACE EXCEPTION DURING RESPONSE
IRequestHandler replacement = new IRequestHandler()
{
@Override
public void respond(IRequestCycle requestCycle)
{
responses++;
}
@Override
public void detach(IRequestCycle requestCycle)
{
detaches++;
}
};
cycle = newRequestCycle(new ReplaceHandlerException(replacement, true));
cycle.scheduleRequestHandlerAfterCurrent(new IRequestHandler()
{
@Override
public void respond(IRequestCycle requestCycle)
{
throw new UnsupportedOperationException();
}
@Override
public void detach(IRequestCycle requestCycle)
{
throw new UnsupportedOperationException();
}
});
cycle.processRequestAndDetach();
// 2 resolved, 1 executed, 0 exception mapped
incrementingListener.assertValues(1, 1, 2, 1, 0, 1);
// 0 exception mapped, 1 responded, 2 detached
assertValues(0, 1, 2);
// TEST A REPLACE EXCEPTION DURING RESPONSE
cycle = newRequestCycle(new ReplaceHandlerException(replacement, false));
cycle.scheduleRequestHandlerAfterCurrent(new IRequestHandler()
{
@Override
public void respond(IRequestCycle requestCycle)
{
responses++;
}
@Override
public void detach(IRequestCycle requestCycle)
{
detaches++;
}
});
cycle.processRequestAndDetach();
// 2 resolved, 2 executed, 0 exception mapped
incrementingListener.assertValues(1, 1, 3, 2, 0, 1);
// 0 exception mapped, 2 responded, 3 detached
assertValues(0, 2, 3);
}
/** */
@Test
public void exceptionIsHandledByRegisteredHandler()
{
IncrementingListener incrementingListener = new IncrementingListener();
Application.get().getRequestCycleListeners().add(incrementingListener);
Application.get().getRequestCycleListeners().add(new ErrorCodeListener(401));
RequestCycle cycle = newRequestCycle(new RuntimeException("testing purposes only"));
cycle.processRequestAndDetach();
assertEquals(401, errorCode);
assertEquals(2, incrementingListener.resolved);
assertEquals(1, incrementingListener.executed);
assertEquals(1, incrementingListener.exceptionResolutions);
assertEquals(0, incrementingListener.schedules);
}
/** */
@Test
public void exceptionIsHandledByFirstAvailableHandler()
{
// when two listeners return a handler
Application.get().getRequestCycleListeners().add(new ErrorCodeListener(401));
Application.get().getRequestCycleListeners().add(new ErrorCodeListener(402));
RequestCycle cycle = newRequestCycle(new RuntimeException("testing purposes only"));
cycle.processRequestAndDetach();
// the first handler returned is used to handle the exception
assertEquals(401, errorCode);
}
/**
* @throws Exception
*/
@Test
public void exceptionHandlingInOnDetach() throws Exception
{
// this test is a little flaky because it depends on the ordering of listeners which is not
// guaranteed
RequestCycle cycle = newRequestCycle((RuntimeException)null);
IncrementingListener incrementingListener = new IncrementingListener()
{
@Override
public void onDetach(final RequestCycle cycle)
{
super.onDetach(cycle);
throw new RuntimeException();
}
};
cycle.getListeners().add(incrementingListener);
cycle.getListeners().add(incrementingListener);
cycle.getListeners().add(incrementingListener);
cycle.processRequestAndDetach();
incrementingListener.assertValues(3, 3, 3, 3, 0, 3);
assertValues(0, 1, 1);
}
private void assertValues(int exceptionsMapped, int responses, int detaches)
{
assertEquals(exceptionsMapped, this.exceptionsMapped);
assertEquals(responses, this.responses);
assertEquals(detaches, this.detaches);
this.exceptionsMapped = 0;
this.responses = 0;
this.detaches = 0;
}
private class ErrorCodeListener implements IRequestCycleListener
{
private final int code;
public ErrorCodeListener(int code)
{
this.code = code;
}
@Override
public IRequestHandler onException(final RequestCycle cycle, Exception ex)
{
return requestCycle -> errorCode = code;
}
}
private class IncrementingListener implements IRequestCycleListener
{
private int begins, ends, exceptions, detachesnotified, resolved, exceptionResolutions,
schedules, executed = 0;
@Override
public IRequestHandler onException(final RequestCycle cycle, Exception ex)
{
exceptions++;
return null;
}
@Override
public void onEndRequest(final RequestCycle cycle)
{
ends++;
}
@Override
public void onBeginRequest(final RequestCycle cycle)
{
assertNotNull(RequestCycle.get());
begins++;
}
@Override
public void onDetach(final RequestCycle cycle)
{
detachesnotified++;
}
@Override
public void onRequestHandlerResolved(final RequestCycle cycle, IRequestHandler handler)
{
resolved++;
}
@Override
public void onExceptionRequestHandlerResolved(final RequestCycle cycle,
IRequestHandler handler, Exception exception)
{
exceptionResolutions++;
}
@Override
public void onRequestHandlerScheduled(final RequestCycle cycle, IRequestHandler handler)
{
schedules++;
}
private void assertValues(int begins, int ends, int resolved, int executed, int exceptions, int detachesnotified)
{
assertEquals(begins, this.begins);
assertEquals(ends, this.ends);
assertEquals(resolved, this.resolved);
assertEquals(executed, this.executed);
assertEquals(exceptions, this.exceptions);
assertEquals(detachesnotified, this.detachesnotified);
this.begins = 0;
this.ends = 0;
this.resolved = 0;
this.executed = 0;
this.exceptions = 0;
this.detachesnotified = 0;
}
@Override
public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler)
{
executed++;
}
@Override
public void onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url)
{
}
}
}