/*
* 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;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IRedirectListener;
import org.apache.wicket.IRequestHandler;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestContext;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.RequestListenerInterface;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.Session;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.authorization.AuthorizationException;
import org.apache.wicket.authorization.UnauthorizedActionException;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.html.INewBrowserWindowListener;
import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
import org.apache.wicket.ng.request.Url;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.WebResponse;
import org.apache.wicket.protocol.http.request.WebErrorCodeResponseHandler;
import org.apache.wicket.protocol.http.request.WebExternalResourceRequestTarget;
import org.apache.wicket.request.target.IEventProcessor;
import org.apache.wicket.request.target.component.BookmarkableListenerInterfaceRequestTarget;
import org.apache.wicket.request.target.component.BookmarkablePageRequestTarget;
import org.apache.wicket.request.target.component.PageRequestTarget;
import org.apache.wicket.request.target.component.listener.RedirectPageRequestTarget;
import org.apache.wicket.request.target.resource.SharedResourceRequestTarget;
import org.apache.wicket.settings.IExceptionSettings;
import org.apache.wicket.util.string.Strings;
/**
* Default abstract implementation of {@link IRequestCycleProcessor}.
*
* @author eelcohillenius
*/
public abstract class AbstractRequestCycleProcessor implements IRequestCycleProcessor
{
/** request coding strategy to use. */
private IRequestCodingStrategy requestCodingStrategy;
/**
* Construct.
*/
public AbstractRequestCycleProcessor()
{
}
/**
* @see org.apache.wicket.request.IRequestCycleProcessor#getRequestCodingStrategy()
*/
public IRequestCodingStrategy getRequestCodingStrategy()
{
if (requestCodingStrategy == null)
{
requestCodingStrategy = newRequestCodingStrategy();
}
return requestCodingStrategy;
}
/**
* @see org.apache.wicket.request.IRequestCycleProcessor#processEvents(org.apache.wicket.RequestCycle)
*/
public void processEvents(RequestCycle requestCycle)
{
IRequestHandler target = requestCycle.getRequestTarget();
if (target instanceof IEventProcessor)
{
Application.get().logEventTarget(target);
((IEventProcessor)target).processEvents(requestCycle);
}
}
/**
* @see org.apache.wicket.request.IRequestCycleProcessor#respond(org.apache.wicket.RequestCycle)
*/
public void respond(RequestCycle requestCycle)
{
IRequestHandler requestTarget = requestCycle.getRequestTarget();
if (requestTarget != null)
{
Application.get().logResponseTarget(requestTarget);
requestTarget.respond(requestCycle);
}
}
/**
* @see org.apache.wicket.request.IRequestCycleProcessor#respond(java.lang.RuntimeException,
* org.apache.wicket.RequestCycle)
*/
public void respond(RuntimeException e, RequestCycle requestCycle)
{
// If application doesn't want debug info showing up for users
final Application application = Application.get();
final IExceptionSettings settings = application.getExceptionSettings();
final Page responsePage = requestCycle.getResponsePage();
Page override = onRuntimeException(responsePage, e);
if (override != null)
{
throw new RestartResponseException(override);
}
else if (e instanceof AuthorizationException)
{
// are authorization exceptions always thrown before the real
// render?
// else we need to make a page (see below) or set it hard to a
// redirect.
Class<? extends Page> accessDeniedPageClass = application.getApplicationSettings()
.getAccessDeniedPage();
throw new RestartResponseAtInterceptPageException(accessDeniedPageClass);
}
else if (e instanceof PageExpiredException)
{
Class<? extends Page> pageExpiredErrorPageClass = application.getApplicationSettings()
.getPageExpiredErrorPage();
boolean mounted = isPageMounted(pageExpiredErrorPageClass);
RequestCycle.get().setRedirect(mounted);
throw new RestartResponseException(pageExpiredErrorPageClass);
}
else if (settings.getUnexpectedExceptionDisplay() != IExceptionSettings.SHOW_NO_EXCEPTION_PAGE)
{
// we do not want to redirect - we want to inline the error output
// and preserve the url so when the refresh button is pressed we
// rerun the code that caused the error
// However we don't what to do this in a situation where we are in portlet mode
if (!RequestContext.get().isPortletRequest())
{
requestCycle.setRedirect(false);
}
// figure out which error page to show
Class<? extends Page> internalErrorPageClass = application.getApplicationSettings()
.getInternalErrorPage();
Class<? extends Page> responseClass = responsePage != null ? responsePage.getClass()
: null;
if (responseClass != internalErrorPageClass &&
settings.getUnexpectedExceptionDisplay() == IExceptionSettings.SHOW_INTERNAL_ERROR_PAGE)
{
throw new RestartResponseException(internalErrorPageClass);
}
else if (responseClass != ExceptionErrorPage.class)
{
// Show full details
throw new RestartResponseException(new ExceptionErrorPage(e, responsePage));
}
else
{
// give up while we're ahead!
throw new WicketRuntimeException("Internal Error: Could not render error page " +
internalErrorPageClass, e);
}
}
else if (requestCycle.getResponse() instanceof WebResponse)
{
((WebResponse)requestCycle.getResponse()).getHttpServletResponse().setStatus(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
/**
* Checks whether the given <code>pageClass</code> is mounted.
*
* @param pageClass
* the <code>Class</code> of the <code>Page</code> to be checked
* @return true if the given <code>pageClass</code> is mounted, false otherwise
*/
private boolean isPageMounted(Class<? extends Page> pageClass)
{
RequestCycle.get();
CharSequence path = getRequestCodingStrategy().pathForTarget(
new BookmarkablePageRequestTarget(pageClass));
return path != null;
}
/**
* Creates a new request coding strategy instance. this is (typically) called once at the first
* time {@link #getRequestCodingStrategy()} is called.
*
* @return a new request coding strategy
*/
protected abstract IRequestCodingStrategy newRequestCodingStrategy();
/**
* This method is called when a runtime exception is thrown, just before the actual handling of
* the runtime exception. This implementation passes the call through to
* {@link RequestCycle#onRuntimeException(Page, RuntimeException)}. Note that if you override
* this method {@link RequestCycle#onRuntimeException(Page, RuntimeException)} will not be
* supported.
*
* @param page
* Any page context where the exception was thrown
* @param e
* The exception
* @return Any error page to redirect to
*/
protected Page onRuntimeException(final Page page, final RuntimeException e)
{
return RequestCycle.get().onRuntimeException(page, e);
}
/**
* Resolves to a bookmarkable page target.
*
* @param requestCycle
* the current request cycle
* @param requestParameters
* the request parameters object
* @return the bookmarkable page as a request target
*/
@SuppressWarnings("unchecked")
protected IRequestHandler resolveBookmarkablePage(final RequestCycle requestCycle,
final ObsoleteRequestParameters requestParameters)
{
String bookmarkablePageClass = requestParameters.getBookmarkablePageClass();
Session session = requestCycle.getSession();
Class<? extends Page> pageClass;
try
{
pageClass = (Class<? extends Page>)session.getClassResolver().resolveClass(
bookmarkablePageClass);
}
catch (ClassNotFoundException e)
{
return new WebErrorCodeResponseHandler(HttpServletResponse.SC_NOT_FOUND,
"Unable to load Bookmarkable Page");
}
try
{
PageParameters params = new PageParameters(requestParameters.getParameters());
if (requestParameters.getComponentPath() != null &&
requestParameters.getInterfaceName() != null)
{
// try to retrieve an existing page
final String componentPath = requestParameters.getComponentPath();
final String id = Strings.firstPathComponent(componentPath,
Component.PATH_SEPARATOR);
final Page page = (Page)session.getPageManager().getPage(Integer.valueOf(id));
if (page != null && page.getClass() == pageClass)
{
return resolveListenerInterfaceTarget(requestCycle, page, componentPath,
requestParameters.getInterfaceName(), requestParameters);
}
else
{
// create a new page
return new BookmarkableListenerInterfaceRequestTarget(
requestParameters.getPageMapName(), pageClass, params,
requestParameters.getComponentPath(), requestParameters.getInterfaceName(),
requestParameters.getVersionNumber());
}
}
else
{
return new BookmarkablePageRequestTarget(pageClass, params);
}
}
catch (RuntimeException e)
{
throw new WicketRuntimeException("Unable to instantiate Page class: " +
bookmarkablePageClass + ". See below for details.", e);
}
}
/**
* Resolves to an external resource.
*
* @param requestCycle
* The current request cycle
* @return The external resource request target
*/
protected IRequestHandler resolveExternalResource(RequestCycle requestCycle)
{
// Get the relative URL we need for loading the resource from
// the servlet context
// NOTE: we NEED to put the '/' in front as otherwise some versions
// of application servers (e.g. Jetty 5.1.x) will fail for requests
// like '/mysubdir/myfile.css'
Url url = new Url(requestCycle.getRequest().getUrl());
if (!url.isAbsolute())
{
url.makeAbsolute();
}
return new WebExternalResourceRequestTarget(url.toString());
}
/**
* Resolves to a home page target.
*
* @param requestCycle
* the current request cycle.
* @param requestParameters
* the request parameters object
* @return the home page as a request target
*/
protected IRequestHandler resolveHomePageTarget(final RequestCycle requestCycle,
final ObsoleteRequestParameters requestParameters)
{
Session session = requestCycle.getSession();
Application application = session.getApplication();
try
{
// Get the home page class
Class<? extends Page> homePageClass = application.getHomePage();
PageParameters parameters = new PageParameters(requestParameters.getParameters());
// and create a dummy target for looking up whether the home page is
// mounted
BookmarkablePageRequestTarget homepageTarget = new BookmarkablePageRequestTarget(
homePageClass, parameters);
IRequestCodingStrategy requestCodingStrategy = requestCycle.getProcessor()
.getRequestCodingStrategy();
CharSequence path = requestCodingStrategy.pathForTarget(homepageTarget);
if (path != null)
{
// The home page was mounted at the given path.
// Issue a redirect to that path
requestCycle.setRedirect(true);
}
// else the home page was not mounted; render it now so
// that we will keep a clean path
return homepageTarget;
}
catch (MarkupException e)
{
// Markup exception should pass without modification. They show
// a nice error page
throw e;
}
catch (WicketRuntimeException e)
{
throw new WicketRuntimeException("Could not create home page", e);
}
}
/**
* Resolves the RequestTarget for the given interface. This method can be overridden if some
* special interface needs to resolve to its own target.
*
* @param requestCycle
* The current RequestCycle object
* @param page
* The page object which holds the component for which this interface is called on.
* @param componentPath
* The component path for looking up the component in the page.
* @param interfaceName
* The interface to resolve.
* @param requestParameters
* @return The RequestTarget that was resolved
*/
protected IRequestHandler resolveListenerInterfaceTarget(final RequestCycle requestCycle,
final Page page, final String componentPath, final String interfaceName,
final ObsoleteRequestParameters requestParameters)
{
if (page == null)
{
throw new IllegalArgumentException("page must not be null");
}
if (interfaceName == null)
{
throw new IllegalArgumentException("interfaceName must not be null");
}
if (interfaceName.equals(IRedirectListener.INTERFACE.getName()))
{
return new RedirectPageRequestTarget(page);
}
else if (interfaceName.equals(INewBrowserWindowListener.INTERFACE.getName()))
{
return INewBrowserWindowListener.INTERFACE.newRequestTarget(page, page,
INewBrowserWindowListener.INTERFACE, requestParameters);
}
else
{
// Get the listener interface we need to call
final RequestListenerInterface listener = RequestListenerInterface.forName(interfaceName);
if (listener == null)
{
throw new WicketRuntimeException(
"Attempt to access unknown request listener interface " + interfaceName);
}
// Get component
Component component;
final String pageRelativeComponentPath = Strings.afterFirstPathComponent(componentPath,
Component.PATH_SEPARATOR);
if (Strings.isEmpty(pageRelativeComponentPath))
{
component = page;
}
else
{
component = page.get(pageRelativeComponentPath);
}
if (component == null)
{
throw new WicketRuntimeException("component " + pageRelativeComponentPath +
" not found on page " + page.getClass().getName() + "[id = " +
page.getNumericId() + "], listener interface = " + listener);
}
if (!component.isEnableAllowed())
{
throw new UnauthorizedActionException(component, Component.ENABLE);
}
// Ask the request listener interface object to create a request
// target
return listener.newRequestTarget(page, component, listener, requestParameters);
}
}
/**
* Resolves to a page target that was previously rendered. Optionally resolves to a component
* call target, which is a specialization of a page target. If no corresponding page could be
* found, a expired page target will be returned.
*
* @param requestCycle
* the current request cycle
* @param requestParameters
* the request parameters object
* @return the previously rendered page as a request target
*/
protected IRequestHandler resolveRenderedPage(final RequestCycle requestCycle,
final ObsoleteRequestParameters requestParameters)
{
final String componentPath = requestParameters.getComponentPath();
final Session session = requestCycle.getSession();
final String id = Strings.firstPathComponent(componentPath, Component.PATH_SEPARATOR);
final Page page = (Page)session.getPageManager().getPage(Integer.valueOf(id));
// Does page exist?
if (page != null)
{
// see whether this resolves to a component call or just the page
final String interfaceName = requestParameters.getInterfaceName();
if (interfaceName != null)
{
return resolveListenerInterfaceTarget(requestCycle, page, componentPath,
interfaceName, requestParameters);
}
else
{
return new PageRequestTarget(page);
}
}
// just return null here and let it be handled further down the road.
return null;
}
/**
* Resolves to a shared resource target.
*
* @param requestCycle
* the current request cycle
* @param requestParameters
* the request parameters object
* @return the shared resource as a request target
*/
protected IRequestHandler resolveSharedResource(final RequestCycle requestCycle,
final ObsoleteRequestParameters requestParameters)
{
return new SharedResourceRequestTarget(requestParameters);
}
}