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.Application; 020import org.apache.wicket.core.request.mapper.IPageSource; 021import org.apache.wicket.core.request.mapper.StalePageException; 022import org.apache.wicket.protocol.http.PageExpiredException; 023import org.apache.wicket.request.IRequestHandler; 024import org.apache.wicket.request.IRequestMapper; 025import org.apache.wicket.request.component.IRequestablePage; 026import org.apache.wicket.request.mapper.parameter.PageParameters; 027import org.apache.wicket.util.io.IClusterable; 028import org.apache.wicket.util.lang.Args; 029 030/** 031 * Provides page instance for request handlers. Each of the constructors has just enough information 032 * to get existing or create new page instance. Requesting or creating page instance is deferred 033 * until {@link #getPageInstance()} is called. 034 * <p> 035 * Purpose of this class is to reduce complexity of both {@link IRequestMapper}s and 036 * {@link IRequestHandler}s. {@link IRequestMapper} examines the URL, gathers all relevant 037 * information about the page in the URL (combination of page id, page class, page parameters and 038 * render count), creates {@link PageProvider} object and creates a {@link IRequestHandler} instance 039 * that can use the {@link PageProvider} to access the page. 040 * <p> 041 * Apart from simplifying {@link IRequestMapper}s and {@link IRequestHandler}s {@link PageProvider} 042 * also helps performance because creating or obtaining page from {@link org.apache.wicket.page.IPageManager} is delayed 043 * until the {@link IRequestHandler} actually requires the page. 044 * 045 * @author Matej Knopp 046 */ 047public class PageProvider implements IPageProvider, IClusterable 048{ 049 private static final long serialVersionUID = 1L; 050 051 private final Integer renderCount; 052 053 private final Integer pageId; 054 055 private transient IPageSource pageSource; 056 057 private Class<? extends IRequestablePage> pageClass; 058 059 private PageParameters pageParameters; 060 061 private transient Provision provision; 062 063 /** 064 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 065 * will return page instance with specified id. 066 * 067 * @param pageId 068 * @param renderCount 069 * optional argument 070 */ 071 public PageProvider(final Integer pageId, final Integer renderCount) 072 { 073 this.pageId = pageId; 074 this.renderCount = renderCount; 075 } 076 077 /** 078 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 079 * will return page instance with specified id if it exists and it's class matches pageClass. If 080 * none of these is true new page instance will be created. 081 * 082 * @param pageId 083 * @param pageClass 084 * @param renderCount 085 * optional argument 086 */ 087 public PageProvider(final Integer pageId, final Class<? extends IRequestablePage> pageClass, 088 Integer renderCount) 089 { 090 this(pageId, pageClass, new PageParameters(), renderCount); 091 } 092 093 /** 094 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 095 * will return page instance with specified id if it exists and it's class matches pageClass. If 096 * none of these is true new page instance will be created. 097 * 098 * @param pageId 099 * @param pageClass 100 * @param pageParameters 101 * @param renderCount 102 * optional argument 103 */ 104 public PageProvider(final Integer pageId, final Class<? extends IRequestablePage> pageClass, 105 final PageParameters pageParameters, final Integer renderCount) 106 { 107 this.pageId = pageId; 108 setPageClass(pageClass); 109 setPageParameters(pageParameters); 110 this.renderCount = renderCount; 111 } 112 113 /** 114 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 115 * will return new instance of page with specified class. 116 * 117 * @param pageClass 118 * @param pageParameters 119 */ 120 public PageProvider(final Class<? extends IRequestablePage> pageClass, 121 final PageParameters pageParameters) 122 { 123 setPageClass(pageClass); 124 if (pageParameters != null) 125 { 126 setPageParameters(pageParameters); 127 } 128 pageId = null; 129 renderCount = null; 130 } 131 132 /** 133 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 134 * will return new instance of page with specified class. 135 * 136 * @param pageClass 137 */ 138 public PageProvider(Class<? extends IRequestablePage> pageClass) 139 { 140 this(pageClass, null); 141 } 142 143 /** 144 * Creates a new page provider object. Upon calling of {@link #getPageInstance()} this provider 145 * will return the given page instance. 146 * 147 * @param page 148 */ 149 public PageProvider(IRequestablePage page) 150 { 151 Args.notNull(page, "page"); 152 153 provision = new Provision().resolveTo(page); 154 pageId = page.getPageId(); 155 renderCount = page.getRenderCount(); 156 } 157 158 private Provision getProvision() 159 { 160 if (provision == null) 161 { 162 provision = new Provision().resolve(); 163 } 164 return provision; 165 } 166 167 @Override 168 public IRequestablePage getPageInstance() throws PageExpiredException 169 { 170 IRequestablePage page = getProvision().getPage(); 171 172 if (page == null && wasExpired()) 173 { 174 throw new PageExpiredException("Page with id '" + pageId + "' has expired."); 175 } 176 177 return page; 178 } 179 180 @Override 181 public PageParameters getPageParameters() throws PageExpiredException 182 { 183 if (pageParameters != null) 184 { 185 return pageParameters; 186 } 187 else if (hasPageInstance()) 188 { 189 return getPageInstance().getPageParameters(); 190 } 191 else 192 { 193 return null; 194 } 195 } 196 197 /** 198 * If this provider returns existing page, regardless if it was already created by PageProvider 199 * itself or is or can be found in the data store. The only guarantee is that by calling 200 * {@link PageProvider#getPageInstance()} this provider will return an existing instance and no 201 * page will be created. 202 * 203 * @return if provides an existing page 204 */ 205 @Override 206 public final boolean hasPageInstance() 207 { 208 if (provision != null || pageId != null) 209 { 210 return getProvision().didResolveToPage(); 211 } 212 else 213 { 214 return false; 215 } 216 } 217 218 /** 219 * Returns whether or not the page instance held by this provider has been instantiated by the 220 * provider. 221 * 222 * @return {@code true} iff the page instance held by this provider was instantiated by the 223 * provider 224 */ 225 @Override 226 public final boolean doesProvideNewPage() 227 { 228 return getProvision().doesProvideNewPage(); 229 } 230 231 @Override 232 public boolean wasExpired() 233 { 234 return pageId != null && getProvision().didFailToFindStoredPage(); 235 } 236 237 @Override 238 public Class<? extends IRequestablePage> getPageClass() throws PageExpiredException 239 { 240 if (pageClass != null) 241 { 242 return pageClass; 243 } 244 else 245 { 246 return getPageInstance().getClass(); 247 } 248 } 249 250 protected IPageSource getPageSource() 251 { 252 if (pageSource != null) 253 { 254 return pageSource; 255 } 256 if (Application.exists()) 257 { 258 return Application.get().getMapperContext(); 259 } 260 else 261 { 262 throw new IllegalStateException( 263 "No application is bound to current thread. Call setPageSource() to manually assign pageSource to this provider."); 264 } 265 } 266 267 268 /** 269 * Detaches the page if it has been loaded (that means either 270 * {@link #PageProvider(IRequestablePage)} constructor has been used or 271 * {@link #getPageInstance()} has been called). 272 */ 273 @Override 274 public void detach() 275 { 276 if (provision != null) 277 { 278 provision.detach(); 279 provision = null; 280 } 281 } 282 283 /** 284 * If the {@link PageProvider} is used outside request thread (thread that does not have 285 * application instance assigned) it is necessary to specify a {@link IPageSource} instance so 286 * that {@link PageProvider} knows how to get a page instance. 287 * 288 * @param pageSource 289 */ 290 public void setPageSource(IPageSource pageSource) 291 { 292 if (provision != null) 293 { 294 throw new IllegalStateException("A page was already provided."); 295 } 296 this.pageSource = pageSource; 297 } 298 299 /** 300 * 301 * @param pageClass 302 */ 303 private void setPageClass(Class<? extends IRequestablePage> pageClass) 304 { 305 Args.notNull(pageClass, "pageClass"); 306 307 this.pageClass = pageClass; 308 } 309 310 /** 311 * 312 * @param pageParameters 313 */ 314 protected void setPageParameters(PageParameters pageParameters) 315 { 316 this.pageParameters = pageParameters; 317 } 318 319 /** 320 * 321 * @return page id 322 */ 323 @Override 324 public Integer getPageId() 325 { 326 return pageId; 327 } 328 329 @Override 330 public Integer getRenderCount() 331 { 332 return renderCount; 333 } 334 335 @Override 336 public String toString() 337 { 338 return "PageProvider{" + "renderCount=" + renderCount + ", pageId=" + pageId 339 + ", pageClass=" + pageClass + ", pageParameters=" + pageParameters + '}'; 340 } 341 342 /** 343 * A provision is the work necessary to provide a page. It includes to resolve parameters to a 344 * page, to track the resolution metadata and to keep a reference of the resolved page. 345 * 346 * The logic based on {@link PageProvider}'s parameters: 347 * 348 * - having an stored page id, the stored page is provided 349 * 350 * - having only a page class, a new instance of it is provided 351 * 352 * - having non stored page id plus page class, a new instance of the page class is provided 353 * 354 * - having non stored page id and no page class, no page is provided 355 * 356 * - being a page instance, the instance itself will be the provided page 357 * 358 * @author pedro 359 */ 360 private class Provision 361 { 362 transient IRequestablePage page; 363 boolean failedToFindStoredPage; 364 365 IRequestablePage getPage() 366 { 367 if (page == null && doesProvideNewPage()) 368 { 369 page = getPageSource().newPageInstance(pageClass, pageParameters); 370 } 371 return page; 372 } 373 374 boolean didResolveToPage() 375 { 376 return page != null; 377 } 378 379 boolean doesProvideNewPage() 380 { 381 return (pageId == null || failedToFindStoredPage) && pageClass != null; 382 } 383 384 boolean didFailToFindStoredPage() 385 { 386 return failedToFindStoredPage; 387 } 388 389 Provision resolveTo(IRequestablePage page) 390 { 391 this.page = page; 392 393 return this; 394 } 395 396 Provision resolve() 397 { 398 399 if (pageId != null) 400 { 401 IRequestablePage stored = getPageSource().getPageInstance(pageId); 402 if (stored != null && (pageClass == null || pageClass.equals(stored.getClass()))) 403 { 404 405 page = stored; 406 407 if (renderCount != null && page.getRenderCount() != renderCount) 408 throw new StalePageException(page); 409 } 410 411 failedToFindStoredPage = page == null; 412 } 413 414 return this; 415 } 416 417 void detach() 418 { 419 if (page != null) 420 { 421 page.detach(); 422 } 423 } 424 425 } 426}