001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.core.request.handler; 018 019import org.apache.wicket.Component; 020import org.apache.wicket.IRequestListener; 021import org.apache.wicket.Page; 022import org.apache.wicket.WicketRuntimeException; 023import org.apache.wicket.behavior.Behavior; 024import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy; 025import org.apache.wicket.core.request.handler.logger.ListenerLogData; 026import org.apache.wicket.request.ILoggableRequestHandler; 027import org.apache.wicket.request.IRequestCycle; 028import org.apache.wicket.request.component.IRequestableComponent; 029import org.apache.wicket.request.component.IRequestablePage; 030import org.apache.wicket.request.http.WebRequest; 031import org.apache.wicket.request.mapper.parameter.PageParameters; 032import org.apache.wicket.util.lang.Args; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * Request handler that invokes an {@link IRequestListener} on component or behavior and renders page afterwards. 038 * 039 * @author Matej Knopp 040 */ 041public class ListenerRequestHandler 042 implements 043 IPageRequestHandler, 044 IComponentRequestHandler, 045 ILoggableRequestHandler 046{ 047 048 private static final Logger LOG = LoggerFactory.getLogger(ListenerRequestHandler.class); 049 050 private final IPageAndComponentProvider pageComponentProvider; 051 052 private final Integer behaviorId; 053 054 private ListenerLogData logData; 055 056 /** 057 * Construct. 058 * 059 * @param pageComponentProvider 060 * @param behaviorIndex 061 */ 062 public ListenerRequestHandler(IPageAndComponentProvider pageComponentProvider, Integer behaviorIndex) 063 { 064 Args.notNull(pageComponentProvider, "pageComponentProvider"); 065 066 this.pageComponentProvider = pageComponentProvider; 067 behaviorId = behaviorIndex; 068 } 069 070 /** 071 * Construct. 072 * 073 * @param pageComponentProvider 074 */ 075 public ListenerRequestHandler(PageAndComponentProvider pageComponentProvider) 076 { 077 this(pageComponentProvider, null); 078 } 079 080 public boolean includeRenderCount() 081 { 082 if (behaviorId == null) 083 { 084 return ((IRequestListener)getComponent()).rendersPage(); 085 } 086 else 087 { 088 return ((IRequestListener)getComponent().getBehaviorById(getBehaviorIndex())) 089 .rendersPage(); 090 } 091 } 092 093 @Override 094 public IRequestableComponent getComponent() 095 { 096 return pageComponentProvider.getComponent(); 097 } 098 099 @Override 100 public IRequestablePage getPage() 101 { 102 return pageComponentProvider.getPageInstance(); 103 } 104 105 @Override 106 public Class<? extends IRequestablePage> getPageClass() 107 { 108 return pageComponentProvider.getPageClass(); 109 } 110 111 @Override 112 public Integer getPageId() 113 { 114 return pageComponentProvider.getPageId(); 115 } 116 117 @Override 118 public PageParameters getPageParameters() 119 { 120 return pageComponentProvider.getPageParameters(); 121 } 122 123 @Override 124 public void detach(IRequestCycle requestCycle) 125 { 126 if (logData == null) 127 { 128 logData = new ListenerLogData(pageComponentProvider, behaviorId); 129 } 130 pageComponentProvider.detach(); 131 } 132 133 /** 134 * Index of target behavior or <code>null</code> if component is the target. 135 * 136 * @return behavior index or <code>null</code> 137 */ 138 public Integer getBehaviorIndex() 139 { 140 return behaviorId; 141 } 142 143 @Override 144 public void respond(final IRequestCycle requestCycle) 145 { 146 final IRequestablePage page = getPage(); 147 final boolean freshPage = pageComponentProvider.doesProvideNewPage(); 148 final boolean isAjax = ((WebRequest)requestCycle.getRequest()).isAjax(); 149 150 IRequestableComponent component; 151 try 152 { 153 component = getComponent(); 154 } 155 catch (ComponentNotFoundException e) 156 { 157 // either the page is stateless and the component we are looking for is not added in the 158 // constructor 159 // or the page is stateful+stale and a new instances was created by pageprovider 160 // we denote this by setting component to null 161 component = null; 162 } 163 164 if ((component == null && !freshPage) || (component != null && component.getPage() != page)) 165 { 166 throw new ComponentNotFoundException("Component '" + getComponentPath() 167 + "' has been removed from page."); 168 } 169 170 RedirectPolicy policy = page.isPageStateless() 171 ? RedirectPolicy.NEVER_REDIRECT 172 : RedirectPolicy.AUTO_REDIRECT; 173 174 boolean blockIfExpired = component != null && !component.canCallListenerAfterExpiry(); 175 176 boolean lateComponent = component == null && freshPage; 177 178 if ((pageComponentProvider.wasExpired() && blockIfExpired) || lateComponent) 179 { 180 // A request listener is invoked on an expired page or the component couldn't be 181 // determined. The best we can do is to re-paint the newly constructed page. 182 // Reference: WICKET-4454, WICKET-6288 183 184 if (LOG.isDebugEnabled()) 185 { 186 LOG.debug( 187 "An IRequestListener was called but its page/component({}) couldn't be resolved. " 188 + "Scheduling re-create of the page and ignoring the listener interface...", 189 getComponentPath()); 190 } 191 192 if (isAjax) 193 { 194 policy = RedirectPolicy.ALWAYS_REDIRECT; 195 } 196 197 requestCycle.scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler( 198 new PageProvider(page), policy)); 199 return; 200 } 201 202 invokeListener(requestCycle, policy, isAjax); 203 } 204 205 private void invokeListener(IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax) 206 { 207 if (getBehaviorIndex() == null) 208 { 209 invoke(requestCycle, policy, ajax, getComponent()); 210 } 211 else 212 { 213 final Behavior behavior; 214 try 215 { 216 behavior = getComponent().getBehaviorById(behaviorId); 217 } 218 catch (IndexOutOfBoundsException e) 219 { 220 throw new WicketRuntimeException("Couldn't find component behavior.", e); 221 } 222 invoke(requestCycle, policy, ajax, getComponent(), behavior); 223 } 224 } 225 226 /** 227 * Invokes a given interface on a component. 228 * 229 * @param rcomponent 230 * The component 231 * 232 * @throws ListenerInvocationNotAllowedException 233 * when listener invocation attempted on a component that does not allow it 234 */ 235 private final void invoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final IRequestableComponent rcomponent) 236 { 237 // we are in Wicket core land 238 final Component component = (Component)rcomponent; 239 240 if (!component.canCallListener()) 241 { 242 // just return so that we have a silent fail and just re-render the 243 // page 244 LOG.info("component not enabled or visible; ignoring call. Component: " + component); 245 throw new ListenerInvocationNotAllowedException(component, null, 246 "Component rejected interface invocation"); 247 } 248 249 internalInvoke(requestCycle, policy, ajax, component, component); 250 } 251 252 /** 253 * Invokes a given interface on a component's behavior. 254 * 255 * @param rcomponent 256 * The component 257 * @param behavior 258 * @throws ListenerInvocationNotAllowedException 259 * when listener invocation attempted on a component that does not allow it 260 */ 261 private final void invoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final IRequestableComponent rcomponent, final Behavior behavior) 262 { 263 // we are in Wicket core land 264 final Component component = (Component)rcomponent; 265 266 if (!behavior.canCallListener(component)) 267 { 268 LOG.warn("behavior not enabled; ignore call. Behavior {} at component {}", behavior, 269 component); 270 throw new ListenerInvocationNotAllowedException(component, behavior, 271 "Behavior rejected interface invocation. "); 272 } 273 274 internalInvoke(requestCycle, policy, ajax, component, behavior); 275 } 276 277 private void internalInvoke(final IRequestCycle requestCycle, RedirectPolicy policy, boolean ajax, final Component component, final Object target) 278 { 279 // save a reference to the page because the component can be removed 280 // during the invocation of the listener and thus lose its parent 281 Page page = component.getPage(); 282 283 // initialization is required for stateless pages 284 if (!page.isInitialized()) 285 { 286 page.internalInitialize(); 287 } 288 289 IRequestListener requestListener = (IRequestListener)target; 290 291 if (requestListener.rendersPage() && !ajax) 292 { 293 // schedule page render after current request handler is done. this can be 294 // overridden during invocation of listener 295 // method (i.e. by calling RequestCycle#setResponsePage) 296 requestCycle.scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler( 297 new PageProvider(page), policy)); 298 } 299 300 requestListener.onRequest(); 301 } 302 303 @Override 304 public final boolean isPageInstanceCreated() 305 { 306 return pageComponentProvider.hasPageInstance(); 307 } 308 309 @Override 310 public final String getComponentPath() 311 { 312 return pageComponentProvider.getComponentPath(); 313 } 314 315 @Override 316 public final Integer getRenderCount() 317 { 318 return pageComponentProvider.getRenderCount(); 319 } 320 321 @Override 322 public ListenerLogData getLogData() 323 { 324 return logData; 325 } 326}