/*
* 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.core.request.handler;
import org.apache.wicket.Component;
import org.apache.wicket.IRequestListener;
import org.apache.wicket.Page;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy;
import org.apache.wicket.core.request.handler.logger.ListenerLogData;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.request.ILoggableRequestHandler;
import org.apache.wicket.request.IRequestCycle;
import org.apache.wicket.request.component.IRequestableComponent;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Request handler that invokes an {@link IRequestListener} on component or behavior and renders page afterwards.
*
* @author Matej Knopp
*/
public class ListenerRequestHandler
implements
IPageRequestHandler,
IComponentRequestHandler,
ILoggableRequestHandler
{
private static final Logger LOG = LoggerFactory.getLogger(ListenerRequestHandler.class);
private final IPageAndComponentProvider pageComponentProvider;
private final Integer behaviorId;
private ListenerLogData logData;
/**
* Construct.
*
* @param pageComponentProvider
* @param behaviorIndex
*/
public ListenerRequestHandler(IPageAndComponentProvider pageComponentProvider, Integer behaviorIndex)
{
Args.notNull(pageComponentProvider, "pageComponentProvider");
this.pageComponentProvider = pageComponentProvider;
behaviorId = behaviorIndex;
}
/**
* Construct.
*
* @param pageComponentProvider
*/
public ListenerRequestHandler(PageAndComponentProvider pageComponentProvider)
{
this(pageComponentProvider, null);
}
public boolean includeRenderCount() {
if (behaviorId == null) {
return ((IRequestListener)getComponent()).rendersPage();
} else {
return ((IRequestListener)getComponent().getBehaviorById(getBehaviorIndex())).rendersPage();
}
}
@Override
public IRequestableComponent getComponent()
{
return pageComponentProvider.getComponent();
}
@Override
public IRequestablePage getPage()
{
IRequestablePage page = pageComponentProvider.getPageInstance();
if (page == null && pageComponentProvider.wasExpired())
{
throw new PageExpiredException(
"Page with id '" + pageComponentProvider.getPageId() + "' has expired.");
}
return page;
}
@Override
public Class<? extends IRequestablePage> getPageClass()
{
return pageComponentProvider.getPageClass();
}
@Override
public Integer getPageId()
{
return pageComponentProvider.getPageId();
}
@Override
public PageParameters getPageParameters()
{
return pageComponentProvider.getPageParameters();
}
@Override
public void detach(IRequestCycle requestCycle)
{
if (logData == null)
{
logData = new ListenerLogData(pageComponentProvider, behaviorId);
}
pageComponentProvider.detach();
}
/**
* Index of target behavior or <code>null</code> if component is the target.
*
* @return behavior index or <code>null</code>
*/
public Integer getBehaviorIndex()
{
return behaviorId;
}
@Override
public void respond(final IRequestCycle requestCycle)
{
final IRequestablePage page = getPage();
final boolean freshPage = pageComponentProvider.doesProvideNewPage();
final boolean isAjax = ((WebRequest)requestCycle.getRequest()).isAjax();
IRequestableComponent component;
try
{
component = getComponent();
}
catch (ComponentNotFoundException e)
{
// either the page is stateless and the component we are looking for is not added in the
// constructor
// or the page is stateful+stale and a new instances was created by pageprovider
// we denote this by setting component to null
component = null;
}
if ((component == null && !freshPage) || (component != null && component.getPage() != page))
{
throw new ComponentNotFoundException("Component '" + getComponentPath()
+ "' has been removed from page.");
}
if (page instanceof Page)
{
// initialize the page to be able to check whether it is stateless
((Page)page).internalInitialize();
}
RedirectPolicy policy = page.isPageStateless()
? RedirectPolicy.NEVER_REDIRECT
: RedirectPolicy.AUTO_REDIRECT;
boolean blockIfExpired = component != null && !component.canCallListenerAfterExpiry();
boolean lateComponent = component == null && freshPage;
if ((pageComponentProvider.wasExpired() && blockIfExpired) || lateComponent)
{
// A request listener is invoked on an expired page or the component couldn't be
// determined. The best we can do is to re-paint the newly constructed page.
// Reference: WICKET-4454, WICKET-6288
if (LOG.isDebugEnabled())
{
LOG.debug(
"An IRequestListener was called but its page/component({}) couldn't be resolved. "
+ "Scheduling re-create of the page and ignoring the listener interface...",
getComponentPath());
}
if (isAjax)
{
policy = RedirectPolicy.ALWAYS_REDIRECT;
}
requestCycle.scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(
new PageProvider(page), policy));
return;
}
invokeListener(requestCycle, policy, isAjax);
}
private void invokeListener(IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax)
{
if (getBehaviorIndex() == null)
{
invoke(requestCycle, policy, ajax, getComponent());
}
else
{
try
{
Behavior behavior = getComponent().getBehaviorById(behaviorId);
invoke(requestCycle, policy, ajax, getComponent(), behavior);
}
catch (IndexOutOfBoundsException e)
{
throw new WicketRuntimeException("Couldn't find component behavior.", e);
}
}
}
/**
* Invokes a given interface on a component.
*
* @param rcomponent
* The component
*
* @throws ListenerInvocationNotAllowedException
* when listener invocation attempted on a component that does not allow it
*/
private final void invoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final IRequestableComponent rcomponent)
{
// we are in Wicket core land
final Component component = (Component)rcomponent;
if (!component.canCallListener())
{
// just return so that we have a silent fail and just re-render the
// page
LOG.info("component not enabled or visible; ignoring call. Component: " + component);
throw new ListenerInvocationNotAllowedException(component, null,
"Component rejected interface invocation");
}
internalInvoke(requestCycle, policy, ajax, component, component);
}
/**
* Invokes a given interface on a component's behavior.
*
* @param rcomponent
* The component
* @param behavior
* @throws ListenerInvocationNotAllowedException
* when listener invocation attempted on a component that does not allow it
*/
private final void invoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final IRequestableComponent rcomponent, final Behavior behavior)
{
// we are in Wicket core land
final Component component = (Component)rcomponent;
if (!behavior.canCallListener(component))
{
LOG.warn("behavior not enabled; ignore call. Behavior {} at component {}", behavior,
component);
throw new ListenerInvocationNotAllowedException(component, behavior,
"Behavior rejected interface invocation. ");
}
internalInvoke(requestCycle, policy, ajax, component, behavior);
}
private void internalInvoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final Component component, final Object target)
{
// save a reference to the page because the component can be removed
// during the invocation of the listener and thus lose its parent
Page page = component.getPage();
// initialization is required for stateless pages
if (!page.isInitialized())
{
page.internalInitialize();
}
IRequestListener requestListener = (IRequestListener)target;
if (requestListener.rendersPage() && !ajax)
{
// schedule page render after current request handler is done. this can be
// overridden during invocation of listener
// method (i.e. by calling RequestCycle#setResponsePage)
requestCycle.scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(
new PageProvider(page), policy));
}
requestListener.onRequest();
}
@Override
public final boolean isPageInstanceCreated()
{
return pageComponentProvider.hasPageInstance();
}
@Override
public final String getComponentPath()
{
return pageComponentProvider.getComponentPath();
}
@Override
public final Integer getRenderCount()
{
return pageComponentProvider.getRenderCount();
}
@Override
public ListenerLogData getLogData()
{
return logData;
}
}