org.apache.wicket.protocol.http.MockWebApplication Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* 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.protocol.http;
import java.io.File;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IPageMap;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.Session;
import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
import org.apache.wicket.protocol.http.request.WebErrorCodeResponseTarget;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.request.target.coding.WebRequestEncoder;
import org.apache.wicket.request.target.component.BookmarkablePageRequestTarget;
import org.apache.wicket.request.target.component.IBookmarkablePageRequestTarget;
import org.apache.wicket.request.target.component.IPageRequestTarget;
import org.apache.wicket.settings.IRequestCycleSettings;
import org.apache.wicket.util.file.WebApplicationPath;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.tester.BaseWicketTester;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides a mock implementation of a Wicket HTTP based tester that can be used for
* testing. It emulates all of the functionality of an HttpServlet in a controlled, single-threaded
* environment. It is supported with mock objects for WebSession, HttpServletRequest,
* HttpServletResponse and ServletContext.
*
* In its most basic usage you can just create a new MockWebApplication and provide your Wicket
* Application object. This should be sufficient to allow you to construct components and pages and
* so on for testing. To use certain features such as localization you must also call
* setupRequestAndResponse().
*
* The tester takes an optional path attribute that defines a directory on the disk which will
* correspond to the root of the WAR bundle. This can then be used for locating non-tester
* resources.
*
* To actually test the processing of a particular page or component you can also call
* processRequestCycle() to do all the normal work of a Wicket request.
*
* Between calling setupRequestAndResponse() and processRequestCycle() you can get hold of any of
* the objects for initialization. The servlet request object has some handy convenience methods for
* Initializing the request to invoke certain types of pages and components.
*
* After completion of processRequestCycle() you will probably just be testing component states.
* However, you also have full access to the response document (or binary data) and result codes via
* the servlet response object.
*
* IMPORTANT NOTES
*
* - This harness is SINGLE THREADED - there is only one global session. For multi-threaded
* testing you must do integration testing with a full tester server.
*
*
* @author Chris Turner
*/
public class MockWebApplication
{
/** Logging */
private static final Logger log = LoggerFactory.getLogger(MockWebApplication.class);
/** The last rendered page. */
private Page lastRenderedPage;
/** The previously rendered page */
private Page previousRenderedPage;
/** Mock http servlet request. */
private final MockHttpServletRequest servletRequest;
/** Mock http servlet response. */
private final MockHttpServletResponse servletResponse;
/** Mock http servlet session. */
private final MockHttpSession servletSession;
/** Request. */
private WebRequest wicketRequest;
/** Parameters to be set on the next request. */
private Map parametersForNextRequest;
/** Response. */
private WebResponse wicketResponse;
/** Session. */
private WebSession wicketSession;
/** The tester object */
private final WebApplication application;
private final ServletContext context;
private final WicketFilter filter;
private final Set cookiesOfThisSession = new HashSet();
/**
* Must be true to create add a wicket-ajax header to the next request. Will be immediately
* reset to false
*/
private boolean createAjaxRequest = false;
/**
* Create the mock http tester that can be used for testing.
*
* @param application
* The wicket application object
* @param path
* The absolute path on disk to the web tester contents (e.g. war root) - may be null
* @see org.apache.wicket.protocol.http.MockServletContext
*/
public MockWebApplication(final WebApplication application, final String path)
{
parametersForNextRequest = new HashMap();
this.application = application;
context = newServletContext(path);
filter = new WicketFilter()
{
@Override
protected IWebApplicationFactory getApplicationFactory()
{
return new IWebApplicationFactory()
{
public WebApplication createApplication(WicketFilter filter)
{
return application;
};
};
}
};
try
{
filter.init(new FilterConfig()
{
public ServletContext getServletContext()
{
return context;
}
public Enumeration> getInitParameterNames()
{
return null;
}
public String getInitParameter(String name)
{
if (name.equals(WicketFilter.FILTER_MAPPING_PARAM))
{
return WicketFilter.SERVLET_PATH_HOLDER;
// return "/" + MockWebApplication.this.getName() +
// "/*";
}
return null;
}
public String getFilterName()
{
return "WicketMockServlet";
}
});
}
catch (ServletException e)
{
throw new RuntimeException(e);
}
Application.set(this.application);
// Construct mock session, request and response
servletSession = new MockHttpSession(context);
servletSession.setTemporary(initializeHttpSessionAsTemporary());
servletRequest = new MockHttpServletRequest(this.application, servletSession, context);
servletResponse = new MockHttpServletResponse(servletRequest)
{
@Override
public void addCookie(Cookie cookie)
{
super.addCookie(cookie);
// remove any potential duplicates
Iterator iter = cookiesOfThisSession.iterator();
while (iter.hasNext())
{
Cookie entry = iter.next();
if (cookie.getName().equals(entry.getName()))
{
iter.remove();
}
}
// From Cookie javadoc:
// A positive value indicates that the cookie will expire after that many seconds
// have passed. Note that the value is the maximum age when the cookie will expire,
// not the cookie's current age.
// A negative value means that the cookie is not stored persistently and will be
// deleted when the Web browser exits. A zero value causes the cookie to be deleted.
if (cookie.getMaxAge() != 0)
{
cookiesOfThisSession.add(cookie);
}
// Note that Cookie expiration is not yet implemented in WicketTester
}
};
// Construct request and response using factories
wicketRequest = this.application.newWebRequest(servletRequest);
wicketResponse = this.application.newWebResponse(servletResponse);
// Create request cycle
createRequestCycle();
this.application.getRequestCycleSettings().setRenderStrategy(
IRequestCycleSettings.ONE_PASS_RENDER);
// Don't buffer the response, as this can break ajax tests: see WICKET-1264
this.application.getRequestCycleSettings().setBufferResponse(false);
if (this.application.getResourceFinder() == null)
{
this.application.getResourceSettings().setResourceFinder(
new WebApplicationPath(context));
}
this.application.getPageSettings().setAutomaticMultiWindowSupport(false);
// Since the purpose of MockWebApplication is singlethreaded
// programmatic testing it doesn't make much sense to have a
// modification watcher thread started to watch for changes in the
// markup.
// Disabling this also helps test suites with many test cases
// (problems has been noticed with >~300 test cases). The problem
// is that even if the wicket tester is GC'ed the modification
// watcher still runs, taking up file handles and memory, leading
// to "Too many files opened" or a regular OutOfMemoryException
this.application.getResourceSettings().setResourcePollFrequency(null);
}
/**
* Callback to signal the application to create temporary sessions instead of normal sessions.
* This should only be used if you want to test stuff like {@link Session#bind()}. Default
* returns false.
*
* @return true if sessions should be temporary by default, otherwise false
*/
public boolean initializeHttpSessionAsTemporary()
{
return false;
}
/**
* Used to create a new mock servlet context.
*
* @param path
* The absolute path on disk to the web tester contents (e.g. war root) - may be null
* @return ServletContext
*/
public ServletContext newServletContext(final String path)
{
return new MockServletContext(application, path);
}
/**
* Gets the application object.
*
* @return Wicket application
*/
public final WebApplication getApplication()
{
return application;
}
/**
* Get the page that was just rendered by the last request cycle processing.
*
* @return The last rendered page
*/
public Page getLastRenderedPage()
{
return lastRenderedPage;
}
/**
* Get the page that was previously
*
* @return The last rendered page
*/
public Page getPreviousRenderedPage()
{
return previousRenderedPage;
}
/**
* Get the request object so that we can apply configurations to it.
*
* @return The request object
*/
public MockHttpServletRequest getServletRequest()
{
return servletRequest;
}
/**
* Get the response object so that we can apply configurations to it.
*
* @return The response object
*/
public MockHttpServletResponse getServletResponse()
{
return servletResponse;
}
/**
* Get the session object so that we can apply configurations to it.
*
* @return The session object
*/
public MockHttpSession getServletSession()
{
return servletSession;
}
/**
* Get the wicket request object.
*
* @return The wicket request object
*/
public WebRequest getWicketRequest()
{
return wicketRequest;
}
/**
* Get the wicket response object.
*
* @return The wicket response object
*/
public WebResponse getWicketResponse()
{
return wicketResponse;
}
/**
* Get the wicket session.
*
* @return The wicket session object
*/
public WebSession getWicketSession()
{
return wicketSession;
}
/**
* Initialize a new WebRequestCycle and all its dependent objects
*
* @param component
*/
public void processRequestCycle(final Component component)
{
setupRequestAndResponse();
final WebRequestCycle cycle = createRequestCycle();
cycle.request(component);
if (component instanceof Page)
{
lastRenderedPage = (Page)component;
}
postProcessRequestCycle(cycle);
}
/**
* Initialize a new WebRequestCycle and all its dependent objects
*
* @param
*
* @param pageClass
*/
public void processRequestCycle(final Class pageClass)
{
processRequestCycle(pageClass, null);
}
/**
* Initialize a new WebRequestCycle and all its dependent objects
*
* @param
*
* @param pageClass
* @param params
*/
@SuppressWarnings("deprecation")
public void processRequestCycle(final Class pageClass, PageParameters params)
{
final WebRequestCycle cycle = setupRequestAndResponse();
try
{
BaseWicketTester.callOnBeginRequest(cycle);
BookmarkablePageRequestTarget requestTarget = new BookmarkablePageRequestTarget(
pageClass, params);
if (pageClass == application.getHomePage())
{
// special handling
// code is copy pasted from
// org.apache.wicket.protocol.http.request.WebRequestCodingStrategy.encode(
// RequestCycle
// , IBookmarkablePageRequestTarget)
// since only the test is suffering from this problem
// this is a bit ugly and i am open to suggestions on how to fix this better. MM
String pageMapName;
IRequestTarget currentTarget = cycle.getRequestTarget();
if (currentTarget instanceof IPageRequestTarget)
{
Page currentPage = ((IPageRequestTarget)currentTarget).getPage();
final IPageMap pageMap = currentPage.getPageMap();
if (pageMap.isDefault())
{
pageMapName = "";
}
else
{
pageMapName = pageMap.getName();
}
}
else
{
pageMapName = "";
}
AppendingStringBuffer buffer = new AppendingStringBuffer(64);
WebRequestEncoder encoder = new WebRequestEncoder(buffer);
encoder.addValue(WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME,
pageMapName + Component.PATH_SEPARATOR + pageClass.getName());
if (params != null)
{
final Iterator iterator = params.keySet().iterator();
while (iterator.hasNext())
{
final String key = iterator.next();
final String values[] = params.getStringArray(key);
if (values != null)
{
for (int i = 0; i < values.length; i++)
{
encoder.addValue(key, values[i]);
}
}
}
}
String url = buffer.toString();
String path = application.getClass().getName();
path = path.substring(path.lastIndexOf('.') + 1);
path = "/" + path + "/" + path + "/";
getServletRequest().setURL(path + url);
}
else if (application.getHomePage() != null)
{
String url = cycle.urlFor(requestTarget).toString();
String path = application.getClass().getName();
path = path.substring(path.lastIndexOf('.') + 1);
path = "/" + path + "/" + path + "/";
getServletRequest().setURL(path + url);
}
else
{
log.warn("The application does not have a HomePage, this might cause problems or unexpected behavior");
}
cycle.request(requestTarget);
}
finally
{
cycle.getResponse().close();
}
postProcessRequestCycle(cycle);
}
/**
* Create and process the request cycle using the current request and response information.
*/
public void processRequestCycle()
{
processRequestCycle(createRequestCycle());
}
/**
* Create and process the request cycle using the current request and response information.
*
* @param cycle
*/
public void processRequestCycle(WebRequestCycle cycle)
{
try
{
cycle.request();
if (cycle.wasHandled() == false)
{
cycle.setRequestTarget(new WebErrorCodeResponseTarget(
HttpServletResponse.SC_NOT_FOUND));
}
createRequestCycle();
parametersForNextRequest.clear();
servletRequest.getParameterMap().clear();
}
finally
{
cycle.getResponse().close();
}
postProcessRequestCycle(cycle);
}
/**
*
* @param cycle
*/
public final void postProcessRequestCycle(WebRequestCycle cycle)
{
previousRenderedPage = lastRenderedPage;
if (cycle.getResponse() instanceof WebResponse)
{
// handle redirects which are usually managed by the browser
// transparently
final MockHttpServletResponse httpResponse = (MockHttpServletResponse)cycle.getWebResponse()
.getHttpServletResponse();
if (httpResponse.isRedirect())
{
lastRenderedPage = generateLastRenderedPage(cycle);
String redirectLocation = httpResponse.getRedirectLocation();
cycle = setupRequestAndResponse(false);
servletRequest.setRequestToRedirectString(redirectLocation);
cycle.request();
}
else
{
String url = httpResponse.getHeader("Ajax-Location");
if (url != null)
{
MockHttpServletRequest newHttpRequest = new MockHttpServletRequest(application,
servletSession, application.getServletContext());
newHttpRequest.setRequestToRedirectString(url);
wicketRequest = application.newWebRequest(newHttpRequest);
cycle = createRequestCycle();
cycle.request();
}
}
}
lastRenderedPage = generateLastRenderedPage(cycle);
Session.set(getWicketSession());
if (getLastRenderedPage() instanceof ExceptionErrorPage)
{
throw (RuntimeException)((ExceptionErrorPage)getLastRenderedPage()).getThrowable();
}
}
/**
*
* @param cycle
* @return Last page
*/
private Page generateLastRenderedPage(WebRequestCycle cycle)
{
Page newLastRenderedPage = cycle.getResponsePage();
if (newLastRenderedPage == null)
{
Class extends Page> responseClass = cycle.getResponsePageClass();
if (responseClass != null)
{
Session.set(cycle.getSession());
IRequestTarget target = cycle.getRequestTarget();
if (target instanceof IPageRequestTarget)
{
newLastRenderedPage = ((IPageRequestTarget)target).getPage();
}
else if (target instanceof IBookmarkablePageRequestTarget)
{
// create a new request cycle for the newPage call
createRequestCycle();
IBookmarkablePageRequestTarget pageClassRequestTarget = (IBookmarkablePageRequestTarget)target;
Class extends Page> pageClass = pageClassRequestTarget.getPageClass();
PageParameters parameters = pageClassRequestTarget.getPageParameters();
if (parameters == null || parameters.size() == 0)
{
newLastRenderedPage = application.getSessionSettings()
.getPageFactory()
.newPage(pageClass);
}
else
{
newLastRenderedPage = application.getSessionSettings()
.getPageFactory()
.newPage(pageClass, parameters);
}
}
}
}
if (newLastRenderedPage == null)
{
newLastRenderedPage = lastRenderedPage;
}
return newLastRenderedPage;
}
/**
* Create and process the request cycle using the current request and response information.
*
* @return A new and initialized WebRequestCyle
*/
public WebRequestCycle createRequestCycle()
{
// Create a web request cycle using factory
final WebRequestCycle cycle = (WebRequestCycle)application.newRequestCycle(wicketRequest,
wicketResponse);
// Construct session
wicketSession = (WebSession)Session.findOrCreate();
// Set request cycle so it won't detach automatically and clear messages
// we want to check
cycle.setAutomaticallyClearFeedbackMessages(false);
return cycle;
}
/**
* Reset the request and the response back to a starting state and recreate the necessary wicket
* request, response and session objects. The request and response objects can be accessed and
* Initialized at this point.
*
* @param isAjax
* indicates whether the request should be initialized as an ajax request (ajax
* header "Wicket-Ajax" is set)
* @return the constructed {@link WebRequestCycle}
*/
public WebRequestCycle setupRequestAndResponse(boolean isAjax)
{
servletRequest.initialize();
servletResponse.initialize();
servletRequest.addCookies(cookiesOfThisSession);
servletRequest.setParameters(parametersForNextRequest);
if (isAjax)
{
servletRequest.addHeader("Wicket-Ajax", "Yes");
}
parametersForNextRequest.clear();
wicketRequest = application.newWebRequest(servletRequest);
wicketResponse = application.newWebResponse(servletResponse);
WebRequestCycle requestCycle = createRequestCycle();
if (!initializeHttpSessionAsTemporary())
{
application.getSessionStore().bind(wicketRequest, wicketSession);
}
wicketResponse.setAjax(wicketRequest.isAjax());
createAjaxRequest = false;
return requestCycle;
}
/**
* Clears cookie storage
*/
public void clearCookiesOfThisSession()
{
if (cookiesOfThisSession != null)
{
cookiesOfThisSession.clear();
}
}
/**
* Reset the request and the response back to a starting state and recreate the necessary wicket
* request, response and session objects. The request and response objects can be accessed and
* Initialized at this point.
*
* @return the constructed {@link WebRequestCycle}
*/
public WebRequestCycle setupRequestAndResponse()
{
return setupRequestAndResponse(isCreateAjaxRequest());
}
/**
* Gets the parameters to be set on the next request.
*
* @return the parameters to be set on the next request
*/
public Map getParametersForNextRequest()
{
return parametersForNextRequest;
}
/**
* Sets the parameters to be set on the next request.
*
* @param parametersForNextRequest
* the parameters to be set on the next request
*/
public void setParametersForNextRequest(Map parametersForNextRequest)
{
this.parametersForNextRequest = parametersForNextRequest;
}
/**
* clears this mock application
*/
public void destroy()
{
filter.destroy();
File dir = (File)context.getAttribute("javax.servlet.context.tempdir");
deleteDir(dir);
application.internalDestroy();
}
private void deleteDir(File dir)
{
if (dir != null && dir.isDirectory())
{
File[] files = dir.listFiles();
if (files != null)
{
for (int i = 0; i < files.length; i++)
{
File element = files[i];
if (element.isDirectory())
{
deleteDir(element);
}
else
{
element.delete();
}
}
}
dir.delete();
}
}
/**
* Gets createAjaxRequest.
*
* @return createAjaxRequest
*/
public boolean isCreateAjaxRequest()
{
return createAjaxRequest;
}
/**
* Sets createAjaxRequest.
*
* Must be true to create add a wicket-ajax header to the next request. Will be immediately
* reset to false
*
* @param createAjaxRequest
* createAjaxRequest
*/
public void setCreateAjaxRequest(boolean createAjaxRequest)
{
this.createAjaxRequest = createAjaxRequest;
}
}