/* * 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.Map; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.apache.wicket.Application; import org.apache.wicket.Component; 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.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; /** * Relatively small changes to make the mock classes work with a temporary session, so * {@link Session#bind()} behavior and {@link Session#isTemporary()} can be tested.</br> * Maurice</br></br> * * 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. * <p> * 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(). * <p> * 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. * <p> * 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. * <p> * 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. * <p> * 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. * <p> * IMPORTANT NOTES * <ul> * <li>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. * </ul> * * @author Chris Turner */ public class SwarmMockWebApplication { /** The last rendered page. */ private Page lastRenderedPage; /** The previously rendered page */ private Page previousRenderedPage; /** Mock http servlet request. */ private final SwarmMockHttpServletRequest servletRequest; /** Mock http servlet response. */ private final SwarmMockHttpServletResponse servletResponse; /** Mock http servlet session. */ private final SwarmMockHttpSession servletSession; /** Request. */ private WebRequest wicketRequest; /** Parameters to be set on the next request. */ private Map<String, Object> parametersForNextRequest = new HashMap<String, Object>(); /** Response. */ private WebResponse wicketResponse; /** Session. */ private WebSession wicketSession; /** The tester object */ private final WebApplication application; private final ServletContext context; private final WicketFilter filter; /** * 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 SwarmMockWebApplication(final WebApplication application, final String path) { this.application = application; context = newServletContext(path); filter = new WicketFilter() { @Override protected IWebApplicationFactory getApplicationFactory() { return new IWebApplicationFactory() { @SuppressWarnings("hiding") public WebApplication createApplication(WicketFilter filter) { return application; } }; } }; try { filter.init(new FilterConfig() { public ServletContext getServletContext() { return context; } public Enumeration<String> 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 SwarmMockHttpSession(context); servletRequest = new SwarmMockHttpServletRequest(this.application, servletSession, context); servletResponse = new SwarmMockHttpServletResponse(servletRequest); // 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); 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); } /** * 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 SwarmMockHttpServletRequest getServletRequest() { return servletRequest; } /** * Get the response object so that we can apply configurations to it. * * @return The response object */ public SwarmMockHttpServletResponse getServletResponse() { return servletResponse; } /** * Get the session object so that we can apply configurations to it. * * @return The session object */ public SwarmMockHttpSession 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 pageClass */ public void processRequestCycle(final Class< ? extends Page> pageClass) { processRequestCycle(pageClass, null); } /** * Initialize a new WebRequestCycle and all its dependent objects * * @param pageClass * @param params */ public void processRequestCycle(final Class< ? extends Page> pageClass, PageParameters params) { setupRequestAndResponse(); final WebRequestCycle cycle = createRequestCycle(); try { cycle.request(new BookmarkablePageRequestTarget(pageClass, params)); } 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(); createRequestCycle(); } 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 SwarmMockHttpServletResponse httpResponse = (SwarmMockHttpServletResponse) cycle.getWebResponse().getHttpServletResponse(); if (httpResponse.isRedirect()) { lastRenderedPage = generateLastRenderedPage(cycle); SwarmMockHttpServletRequest newHttpRequest = new SwarmMockHttpServletRequest(application, servletSession, application .getServletContext()); newHttpRequest.setRequestToRedirectString(httpResponse.getRedirectLocation()); 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) */ public WebRequestCycle setupRequestAndResponse(boolean isAjax) { servletRequest.initialize(); servletResponse.initialize(); servletRequest.setParameters(parametersForNextRequest); if (isAjax) { servletRequest.addHeader("Wicket-Ajax", "Yes"); } parametersForNextRequest.clear(); wicketRequest = application.newWebRequest(servletRequest); wicketResponse = application.newWebResponse(servletResponse); WebRequestCycle requestCycle = createRequestCycle(); // application.getSessionStore().bind(wicketRequest, wicketSession); wicketResponse.setAjax(wicketRequest.isAjax()); return requestCycle; } /** * 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. */ public WebRequestCycle setupRequestAndResponse() { return setupRequestAndResponse(false); } /** * Gets the parameters to be set on the next request. * * @return the parameters to be set on the next request */ public Map<String, Object> 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<String, Object> parametersForNextRequest) { this.parametersForNextRequest = parametersForNextRequest; } /** * clears this mock application */ public void destroy() { filter.destroy(); File dir = (File) context.getAttribute("javax.servlet.context.tempdir"); deleteDir(dir); } 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(); } } }