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.protocol.http; 018 019import java.io.UnsupportedEncodingException; 020import java.nio.charset.Charset; 021import java.time.Duration; 022import java.util.Collection; 023import java.util.LinkedList; 024import java.util.Locale; 025import java.util.function.Function; 026 027import javax.servlet.ServletContext; 028import javax.servlet.http.HttpServletRequest; 029import javax.servlet.http.HttpServletResponse; 030import javax.servlet.http.HttpSession; 031 032import org.apache.wicket.Application; 033import org.apache.wicket.Page; 034import org.apache.wicket.RuntimeConfigurationType; 035import org.apache.wicket.Session; 036import org.apache.wicket.WicketRuntimeException; 037import org.apache.wicket.ajax.AjaxRequestHandler; 038import org.apache.wicket.ajax.AjaxRequestTarget; 039import org.apache.wicket.ajax.AjaxRequestTargetListenerCollection; 040import org.apache.wicket.coep.CrossOriginEmbedderPolicyConfiguration; 041import org.apache.wicket.coep.CrossOriginEmbedderPolicyRequestCycleListener; 042import org.apache.wicket.coop.CrossOriginOpenerPolicyConfiguration; 043import org.apache.wicket.coop.CrossOriginOpenerPolicyRequestCycleListener; 044import org.apache.wicket.core.request.mapper.IMapperContext; 045import org.apache.wicket.core.request.mapper.MountedMapper; 046import org.apache.wicket.core.request.mapper.PackageMapper; 047import org.apache.wicket.core.request.mapper.ResourceMapper; 048import org.apache.wicket.core.util.file.WebApplicationPath; 049import org.apache.wicket.core.util.resource.ClassPathResourceFinder; 050import org.apache.wicket.csp.CSPHeaderConfiguration; 051import org.apache.wicket.csp.ContentSecurityPolicySettings; 052import org.apache.wicket.markup.MarkupType; 053import org.apache.wicket.markup.head.CssHeaderItem; 054import org.apache.wicket.markup.head.JavaScriptHeaderItem; 055import org.apache.wicket.markup.html.WebPage; 056import org.apache.wicket.markup.html.form.AutoLabelResolver; 057import org.apache.wicket.markup.html.form.AutoLabelTextResolver; 058import org.apache.wicket.markup.html.pages.AccessDeniedPage; 059import org.apache.wicket.markup.html.pages.InternalErrorPage; 060import org.apache.wicket.markup.html.pages.PageExpiredErrorPage; 061import org.apache.wicket.markup.resolver.AutoLinkResolver; 062import org.apache.wicket.protocol.http.servlet.AbstractRequestWrapperFactory; 063import org.apache.wicket.protocol.http.servlet.FilterFactoryManager; 064import org.apache.wicket.protocol.http.servlet.ServletWebRequest; 065import org.apache.wicket.protocol.http.servlet.ServletWebResponse; 066import org.apache.wicket.request.IRequestHandler; 067import org.apache.wicket.request.IRequestMapper; 068import org.apache.wicket.request.Request; 069import org.apache.wicket.request.Response; 070import org.apache.wicket.request.Url; 071import org.apache.wicket.request.cycle.RequestCycle; 072import org.apache.wicket.request.handler.render.WebPageRenderer; 073import org.apache.wicket.request.http.WebRequest; 074import org.apache.wicket.request.http.WebResponse; 075import org.apache.wicket.request.mapper.ICompoundRequestMapper; 076import org.apache.wicket.request.mapper.IRequestMapperDelegate; 077import org.apache.wicket.request.resource.CssResourceReference; 078import org.apache.wicket.request.resource.JavaScriptResourceReference; 079import org.apache.wicket.request.resource.ResourceReference; 080import org.apache.wicket.resource.bundles.ReplacementResourceBundleReference; 081import org.apache.wicket.session.HttpSessionStore; 082import org.apache.wicket.util.crypt.CharEncoding; 083import org.apache.wicket.util.file.FileCleaner; 084import org.apache.wicket.util.file.IFileCleaner; 085import org.apache.wicket.util.file.Path; 086import org.apache.wicket.util.lang.Args; 087import org.apache.wicket.util.lang.PackageName; 088import org.apache.wicket.util.string.Strings; 089import org.apache.wicket.util.watch.IModificationWatcher; 090import org.slf4j.Logger; 091import org.slf4j.LoggerFactory; 092 093 094/** 095 * A web application is a subclass of Application which associates with an instance of WicketServlet 096 * to serve pages over the HTTP protocol. This class is intended to be subclassed by framework 097 * clients to define a web application. 098 * <p> 099 * Application settings are given defaults by the WebApplication() constructor and internalInit 100 * method, such as error page classes appropriate for HTML. WebApplication subclasses can override 101 * these values and/or modify other application settings by overriding the init() method and then by 102 * calling getXXXSettings() to retrieve an interface to a mutable Settings object. Do not do this in 103 * the constructor itself because the defaults will then override your settings. 104 * <p> 105 * If you want to use a filter specific configuration, e.g. using init parameters from the 106 * {@link javax.servlet.FilterConfig} object, you should override the init() method. For example: 107 * 108 * <pre> 109 * public void init() { 110 * String webXMLParameter = getInitParameter("myWebXMLParameter"); 111 * URL schedulersConfig = getServletContext().getResource("/WEB-INF/schedulers.xml"); 112 * ... 113 * </pre> 114 * 115 * @see WicketFilter 116 * @see org.apache.wicket.settings.ApplicationSettings 117 * @see org.apache.wicket.settings.DebugSettings 118 * @see org.apache.wicket.settings.ExceptionSettings 119 * @see org.apache.wicket.settings.MarkupSettings 120 * @see org.apache.wicket.settings.PageSettings 121 * @see org.apache.wicket.settings.RequestCycleSettings 122 * @see org.apache.wicket.settings.ResourceSettings 123 * @see org.apache.wicket.settings.SecuritySettings 124 * @see javax.servlet.Filter 125 * @see javax.servlet.FilterConfig 126 * @see javax.servlet.ServletContext 127 * 128 * @author Jonathan Locke 129 * @author Chris Turner 130 * @author Johan Compagner 131 * @author Eelco Hillenius 132 * @author Juergen Donnerstag 133 */ 134public abstract class WebApplication extends Application 135{ 136 /** Log. */ 137 private static final Logger log = LoggerFactory.getLogger(WebApplication.class); 138 139 public static final String META_INF_RESOURCES = "META-INF/resources"; 140 141 private ServletContext servletContext; 142 143 private final AjaxRequestTargetListenerCollection ajaxRequestTargetListeners; 144 145 private Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider; 146 147 private FilterFactoryManager filterFactoryManager; 148 149 /** 150 * Cached value of the parsed (from system properties or Servlet init/context parameter) 151 * <code>wicket.configuration</code> setting. No need to re-read it because it wont change at 152 * runtime. 153 */ 154 private RuntimeConfigurationType configurationType; 155 156 private ContentSecurityPolicySettings cspSettings; 157 158 /** 159 * Covariant override for easy getting the current {@link WebApplication} without having to cast 160 * it. 161 */ 162 public static WebApplication get() 163 { 164 Application application = Application.get(); 165 166 if (application instanceof WebApplication == false) 167 { 168 throw new WicketRuntimeException( 169 "The application attached to the current thread is not a " + 170 WebApplication.class.getSimpleName()); 171 } 172 173 return (WebApplication)application; 174 } 175 176 /** 177 * the prefix for storing variables in the actual session (typically {@link HttpSession} for 178 * this application instance. 179 */ 180 private String sessionAttributePrefix; 181 182 /** The WicketFilter that this application is attached to */ 183 private WicketFilter wicketFilter; 184 185 /** 186 * Constructor. <strong>Use {@link #init()} for any configuration of your application instead of 187 * overriding the constructor.</strong> 188 */ 189 public WebApplication() 190 { 191 ajaxRequestTargetListeners = new AjaxRequestTargetListenerCollection(); 192 } 193 194 /** 195 * @see org.apache.wicket.Application#getApplicationKey() 196 */ 197 @Override 198 public final String getApplicationKey() 199 { 200 return getName(); 201 } 202 203 /** 204 * Gets an init parameter of the filter, or null if the parameter does not exist. 205 * 206 * @param key 207 * the key to search for 208 * @return the value of the filter init parameter 209 */ 210 public String getInitParameter(String key) 211 { 212 if (wicketFilter != null) 213 { 214 return wicketFilter.getFilterConfig().getInitParameter(key); 215 } 216 throw new IllegalStateException("init parameter '" + key + 217 "' is not set yet. Any code in your" + 218 " Application object that uses the wicketServlet/Filter instance should be put" + 219 " in the init() method instead of your constructor"); 220 } 221 222 223 /** 224 * Sets servlet context this application runs after. This is uaully done from a filter or a 225 * servlet responsible for managing this application object, such as {@link WicketFilter} 226 * 227 * @param servletContext 228 */ 229 public void setServletContext(ServletContext servletContext) 230 { 231 this.servletContext = servletContext; 232 } 233 234 /** 235 * Gets the servlet context for this application. Use this to get references to absolute paths, 236 * global web.xml parameters (<context-param>), etc. 237 * 238 * @return The servlet context for this application 239 */ 240 public ServletContext getServletContext() 241 { 242 if (servletContext == null) 243 { 244 throw new IllegalStateException("servletContext is not set yet. Any code in your" 245 + " Application object that uses the wicket filter instance should be put" 246 + " in the init() method instead of your constructor"); 247 } 248 return servletContext; 249 } 250 251 /** 252 * Gets the prefix for storing variables in the actual session (typically {@link HttpSession} 253 * for this application instance. 254 * 255 * @param request 256 * the request 257 * @param filterName 258 * If null, than it defaults to the WicketFilter filter name. However according to 259 * the ServletSpec the Filter is not guaranteed to be initialized e.g. when 260 * WicketSessionFilter gets initialized. Thus, though you (and WicketSessionFilter) 261 * can provide a filter name, you must make sure it is the same as WicketFilter will 262 * provide once initialized. 263 * 264 * @return the prefix for storing variables in the actual session 265 */ 266 public String getSessionAttributePrefix(final WebRequest request, String filterName) 267 { 268 if (sessionAttributePrefix == null) 269 { 270 if (filterName == null) 271 { 272 // According to the ServletSpec, the filter might not yet been initialized 273 filterName = getWicketFilter().getFilterConfig().getFilterName(); 274 } 275 String namespace = getMapperContext().getNamespace(); 276 sessionAttributePrefix = namespace + ':' + filterName + ':'; 277 } 278 279 // Namespacing for session attributes is provided by 280 // adding the servlet path 281 return sessionAttributePrefix; 282 } 283 284 /** 285 * @return The Wicket filter for this application 286 */ 287 public final WicketFilter getWicketFilter() 288 { 289 return wicketFilter; 290 } 291 292 /** 293 * @see org.apache.wicket.Application#logEventTarget(org.apache.wicket.request.IRequestHandler) 294 */ 295 @Override 296 public void logEventTarget(IRequestHandler target) 297 { 298 super.logEventTarget(target); 299 IRequestLogger rl = getRequestLogger(); 300 if (rl != null) 301 { 302 rl.logEventTarget(target); 303 } 304 } 305 306 /** 307 * @see org.apache.wicket.Application#logResponseTarget(org.apache.wicket.request.IRequestHandler) 308 */ 309 @Override 310 public void logResponseTarget(IRequestHandler target) 311 { 312 super.logResponseTarget(target); 313 IRequestLogger rl = getRequestLogger(); 314 if (rl != null) 315 { 316 rl.logResponseTarget(target); 317 } 318 } 319 320 /** 321 * Mounts an encoder at the given path. 322 * 323 * @param mapper 324 * the encoder that will be used for this mount 325 */ 326 public void mount(final IRequestMapper mapper) 327 { 328 Args.notNull(mapper, "mapper"); 329 getRootRequestMapperAsCompound().add(mapper); 330 } 331 332 /** 333 * Mounts a page class to the given path. 334 * 335 * <p> 336 * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know 337 * which segments are reserved for internal use. 338 * </p> 339 * @param <T> 340 * type of page 341 * 342 * @param path 343 * the path to mount the page class on 344 * @param pageClass 345 * the page class to be mounted 346 */ 347 public <T extends Page> MountedMapper mountPage(final String path, final Class<T> pageClass) 348 { 349 MountedMapper mapper = new MountedMapper(path, pageClass); 350 mount(mapper); 351 return mapper; 352 } 353 354 /** 355 * Mounts a shared resource to the given path. 356 * 357 * <p> 358 * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know 359 * which segments are reserved for internal use. 360 * </p> 361 * @param path 362 * the path to mount the resource reference on 363 * @param reference 364 * resource reference to be mounted 365 */ 366 public ResourceMapper mountResource(final String path, final ResourceReference reference) 367 { 368 if (reference.canBeRegistered()) 369 { 370 getResourceReferenceRegistry().registerResourceReference(reference); 371 } 372 ResourceMapper mapper = new ResourceMapper(path, reference); 373 mount(mapper); 374 return mapper; 375 } 376 377 /** 378 * Mounts mounts all bookmarkable pages in a the pageClass's package to the given path. 379 * 380 * <p> 381 * NOTE: mount path must not start with reserved URL segments! See {@link IMapperContext} to know 382 * which segments are reserved for internal use. 383 * </p> 384 * @param <P> 385 * type of page 386 * 387 * @param path 388 * the path to mount the page class on 389 * @param pageClass 390 * the page class to be mounted 391 */ 392 public <P extends Page> PackageMapper mountPackage(final String path, final Class<P> pageClass) 393 { 394 PackageMapper packageMapper = new PackageMapper(path, PackageName.forClass(pageClass)); 395 mount(packageMapper); 396 return packageMapper; 397 } 398 399 /** 400 * Unregisters all {@link IRequestMapper}s which would match on a this path. 401 * <p> 402 * Useful in OSGi environments where a bundle may want to update the mount point. 403 * </p> 404 * 405 * @param path 406 * the path to unmount 407 */ 408 public void unmount(String path) 409 { 410 Args.notNull(path, "path"); 411 412 if (path.charAt(0) == '/') 413 { 414 path = path.substring(1); 415 } 416 417 IRequestMapper mapper = getRootRequestMapper(); 418 419 while (mapper instanceof IRequestMapperDelegate) 420 { 421 mapper = ((IRequestMapperDelegate) mapper).getDelegateMapper(); 422 } 423 424 /* 425 * Only attempt to unmount if root request mapper is either a compound, or wraps a compound to avoid leaving the 426 * application with no mappers installed. 427 */ 428 if (mapper instanceof ICompoundRequestMapper) 429 { 430 final Url url = Url.parse(path); 431 432 Request request = new Request() 433 { 434 @Override 435 public Url getUrl() 436 { 437 return url; 438 } 439 440 @Override 441 public Url getClientUrl() 442 { 443 return url; 444 } 445 446 @Override 447 public Locale getLocale() 448 { 449 return null; 450 } 451 452 @Override 453 public Charset getCharset() 454 { 455 return null; 456 } 457 458 @Override 459 public Object getContainerRequest() 460 { 461 return null; 462 } 463 }; 464 465 unmountFromCompound((ICompoundRequestMapper) mapper, request); 466 } 467 } 468 469 /** 470 * Descends the tree of {@link ICompoundRequestMapper}s and {@link IRequestMapperDelegate}s to find the correct descendant 471 * to remove. 472 * 473 * @param parent 474 * The {@link ICompoundRequestMapper} from which to unmount the matching mapper. 475 * @param request 476 * The request used to find the mapper to remove. 477 */ 478 private void unmountFromCompound(ICompoundRequestMapper parent, Request request) 479 { 480 Collection<IRequestMapper> toRemove = new LinkedList<>(); 481 482 for (IRequestMapper mapper : parent) 483 { 484 if (mapper.mapRequest(request) != null) 485 { 486 IRequestMapper actualMapper = mapper; 487 488 while (actualMapper instanceof IRequestMapperDelegate) 489 { 490 actualMapper = ((IRequestMapperDelegate) actualMapper).getDelegateMapper(); 491 } 492 493 if (actualMapper instanceof ICompoundRequestMapper) 494 { 495 unmountFromCompound((ICompoundRequestMapper) actualMapper, request); 496 } 497 else 498 { 499 toRemove.add(mapper); 500 } 501 } 502 } 503 504 for (IRequestMapper mapper : toRemove) 505 { 506 parent.remove(mapper); 507 } 508 } 509 510 /** 511 * Registers a replacement resource for the given javascript resource. This replacement can be 512 * another {@link JavaScriptResourceReference} for a packaged resource, but it can also be an 513 * {@link org.apache.wicket.request.resource.UrlResourceReference} to replace the resource by a 514 * resource hosted on a CDN. Registering a replacement will cause the resource to replaced by 515 * the given resource throughout the application: if {@code base} is added, {@code replacement} 516 * will be added instead. 517 * 518 * @param base 519 * The resource to replace 520 * @param replacement 521 * The replacement 522 */ 523 public final void addResourceReplacement(JavaScriptResourceReference base, 524 ResourceReference replacement) 525 { 526 ReplacementResourceBundleReference bundle = new ReplacementResourceBundleReference(replacement); 527 bundle.addProvidedResources(JavaScriptHeaderItem.forReference(base)); 528 getResourceBundles().addBundle(JavaScriptHeaderItem.forReference(bundle)); 529 } 530 531 /** 532 * Registers a replacement resource for the given CSS resource. This replacement can be another 533 * {@link CssResourceReference} for a packaged resource, but it can also be an 534 * {@link org.apache.wicket.request.resource.UrlResourceReference} to replace the resource by a 535 * resource hosted on a CDN. Registering a replacement will cause the resource to replaced by 536 * the given resource throughout the application: if {@code base} is added, {@code replacement} 537 * will be added instead. 538 * 539 * @param base 540 * The resource to replace 541 * @param replacement 542 * The replacement 543 */ 544 public final void addResourceReplacement(CssResourceReference base, 545 ResourceReference replacement) 546 { 547 ReplacementResourceBundleReference bundle = new ReplacementResourceBundleReference(replacement); 548 bundle.addProvidedResources(CssHeaderItem.forReference(base)); 549 getResourceBundles().addBundle(CssHeaderItem.forReference(bundle)); 550 } 551 552 /** 553 * Create a new WebRequest. Subclasses of WebRequest could e.g. decode and obfuscate URL which 554 * has been encoded by an appropriate WebResponse. 555 * 556 * @param servletRequest 557 * the current HTTP Servlet request 558 * @param filterPath 559 * the filter mapping read from web.xml 560 * @return a WebRequest object 561 */ 562 public WebRequest newWebRequest(HttpServletRequest servletRequest, final String filterPath) 563 { 564 return new ServletWebRequest(servletRequest, filterPath); 565 } 566 567 /** 568 * Pre- and post- configures the {@link WebRequest} created by user override-able 569 * {@link #newWebRequest(HttpServletRequest, String)} 570 * 571 * @param servletRequest 572 * the current HTTP Servlet request 573 * @param filterPath 574 * the filter mapping read from web.xml 575 * @return a WebRequest object 576 */ 577 WebRequest createWebRequest(HttpServletRequest servletRequest, final String filterPath) 578 { 579 if (hasFilterFactoryManager()) 580 { 581 for (AbstractRequestWrapperFactory factory : getFilterFactoryManager()) 582 { 583 servletRequest = factory.getWrapper(servletRequest); 584 } 585 } 586 587 WebRequest webRequest = newWebRequest(servletRequest, filterPath); 588 589 if (servletRequest.getCharacterEncoding() == null) 590 { 591 try 592 { 593 if (webRequest.isAjax()) 594 { 595 // WICKET-3908, WICKET-1816: Forms submitted with Ajax are always UTF-8 encoded 596 servletRequest.setCharacterEncoding(CharEncoding.UTF_8); 597 } 598 else 599 { 600 String requestEncoding = getRequestCycleSettings().getResponseRequestEncoding(); 601 servletRequest.setCharacterEncoding(requestEncoding); 602 } 603 } 604 catch (UnsupportedEncodingException e) 605 { 606 throw new WicketRuntimeException(e); 607 } 608 } 609 610 return webRequest; 611 } 612 613 /** 614 * Creates a WebResponse. Subclasses of WebRequest could e.g. encode wicket's default URL and 615 * hide the details from the user. A appropriate WebRequest must be implemented and configured 616 * to decode the encoded URL. 617 * 618 * @param webRequest 619 * the {@link WebRequest} that will handle the current HTTP Servlet request 620 * @param httpServletResponse 621 * the current HTTP Servlet response 622 * @return a WebResponse object 623 */ 624 protected WebResponse newWebResponse(final WebRequest webRequest, 625 final HttpServletResponse httpServletResponse) 626 { 627 return new ServletWebResponse((ServletWebRequest)webRequest, httpServletResponse); 628 } 629 630 /** 631 * Pre- and post- configures the {@link WebResponse} returned from 632 * {@link #newWebResponse(WebRequest, HttpServletResponse)} 633 * 634 * @param webRequest 635 * the {@link WebRequest} that will handle the current HTTP Servlet request 636 * @param httpServletResponse 637 * the current HTTP Servlet response 638 * @return the configured WebResponse object 639 */ 640 WebResponse createWebResponse(final WebRequest webRequest, 641 final HttpServletResponse httpServletResponse) 642 { 643 WebResponse webResponse = newWebResponse(webRequest, httpServletResponse); 644 645 boolean shouldBufferResponse = getRequestCycleSettings().getBufferResponse(); 646 return shouldBufferResponse ? new HeaderBufferingWebResponse(webResponse) : webResponse; 647 } 648 649 /** 650 * @see org.apache.wicket.Application#newSession(org.apache.wicket.request.Request, 651 * org.apache.wicket.request.Response) 652 */ 653 @Override 654 public Session newSession(Request request, Response response) 655 { 656 return new WebSession(request); 657 } 658 659 /** 660 * @see org.apache.wicket.Application#sessionUnbound(java.lang.String) 661 */ 662 @Override 663 public void sessionUnbound(final String sessionId) 664 { 665 super.sessionUnbound(sessionId); 666 667 IRequestLogger logger = getRequestLogger(); 668 if (logger != null) 669 { 670 logger.sessionDestroyed(sessionId); 671 } 672 } 673 674 /** 675 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. 676 * 677 * @param wicketFilter 678 * The wicket filter instance for this application 679 */ 680 public final void setWicketFilter(final WicketFilter wicketFilter) 681 { 682 Args.notNull(wicketFilter, "wicketFilter"); 683 this.wicketFilter = wicketFilter; 684 servletContext = wicketFilter.getFilterConfig().getServletContext(); 685 } 686 687 /** 688 * Initialize; if you need the wicket servlet/filter for initialization, e.g. because you want 689 * to read an initParameter from web.xml or you want to read a resource from the servlet's 690 * context path, you can override this method and provide custom initialization. This method is 691 * called right after this application class is constructed, and the wicket servlet/filter is 692 * set. <strong>Use this method for any application setup instead of the constructor.</strong> 693 */ 694 @Override 695 protected void init() 696 { 697 super.init(); 698 } 699 700 /** 701 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. 702 */ 703 @Override 704 public void internalDestroy() 705 { 706 // destroy the resource watcher 707 IModificationWatcher resourceWatcher = getResourceSettings().getResourceWatcher(false); 708 if (resourceWatcher != null) 709 { 710 resourceWatcher.destroy(); 711 } 712 713 IFileCleaner fileCleaner = getResourceSettings().getFileCleaner(); 714 if (fileCleaner != null) 715 { 716 fileCleaner.destroy(); 717 } 718 719 super.internalDestroy(); 720 } 721 722 /** 723 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT. 724 * 725 * Internal initialization. First determine the deployment mode. First check the system property 726 * -Dwicket.configuration. If it does not exist check the servlet init parameter ( 727 * <code><init-param><param-name>configuration</param-name></code>). If not 728 * found check the servlet context init parameter 729 * <code><context-param><param-name6gt;configuration</param-name></code>). If the 730 * parameter is "development" (which is default), settings appropriate for development are set. 731 * If it's "deployment" , deployment settings are used. If development is specified and a 732 * "sourceFolder" init parameter is also set, then resources in that folder will be polled for 733 * changes. 734 */ 735 @Override 736 protected void internalInit() 737 { 738 super.internalInit(); 739 740 getResourceSettings().getResourceFinders().add( 741 new WebApplicationPath(getServletContext(), "")); 742 getResourceSettings().getResourceFinders().add( 743 new ClassPathResourceFinder(META_INF_RESOURCES)); 744 745 // Set default error pages for HTML markup 746 getApplicationSettings().setPageExpiredErrorPage(PageExpiredErrorPage.class); 747 getApplicationSettings().setInternalErrorPage(InternalErrorPage.class); 748 getApplicationSettings().setAccessDeniedPage(AccessDeniedPage.class); 749 750 // Add resolver for automatically resolving HTML links 751 getPageSettings().addComponentResolver(new AutoLinkResolver()); 752 getPageSettings().addComponentResolver(new AutoLabelResolver()); 753 getPageSettings().addComponentResolver(new AutoLabelTextResolver()); 754 755 getResourceSettings().setFileCleaner(new FileCleaner()); 756 757 setPageRendererProvider(WebPageRenderer::new); 758 setSessionStoreProvider(HttpSessionStore::new); 759 setAjaxRequestTargetProvider(AjaxRequestHandler::new); 760 761 getAjaxRequestTargetListeners().add(new AjaxEnclosureListener()); 762 763 // Configure the app. 764 configure(); 765 if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) 766 { 767 // Add optional sourceFolder for resources. 768 String resourceFolder = getInitParameter("sourceFolder"); 769 if (resourceFolder != null) 770 { 771 getResourceSettings().getResourceFinders().add(new Path(resourceFolder)); 772 } 773 getCspSettings().blocking().reportBack(); 774 } 775 getCspSettings().blocking().strict(); 776 } 777 778 @Override 779 protected void validateInit() 780 { 781 super.validateInit(); 782 783 if (getCspSettings().isEnabled()) { 784 getCspSettings().enforce(this); 785 } 786 787 // enable coop and coep listeners if specified in security settings 788 CrossOriginOpenerPolicyConfiguration coopConfig = getSecuritySettings() 789 .getCrossOriginOpenerPolicyConfiguration(); 790 if (coopConfig.isEnabled()) 791 { 792 getRequestCycleListeners() 793 .add(new CrossOriginOpenerPolicyRequestCycleListener(coopConfig)); 794 } 795 796 CrossOriginEmbedderPolicyConfiguration coepConfig = getSecuritySettings() 797 .getCrossOriginEmbedderPolicyConfiguration(); 798 if (coepConfig.isEnabled()) 799 { 800 getRequestCycleListeners() 801 .add(new CrossOriginEmbedderPolicyRequestCycleListener(coepConfig)); 802 } 803 } 804 805 /** 806 * set runtime configuration type 807 * <p/> 808 * this is a write-once property: once configured it can not be changed later on. 809 * 810 * @param configurationType 811 */ 812 public Application setConfigurationType(RuntimeConfigurationType configurationType) 813 { 814 if (this.configurationType != null) 815 { 816 throw new IllegalStateException( 817 "Configuration type is write-once. You can not change it. " + "" + 818 "Current value='" + configurationType + "'"); 819 } 820 this.configurationType = Args.notNull(configurationType, "configurationType"); 821 return this; 822 } 823 824 /** 825 * {@inheritDoc} 826 */ 827 @Override 828 public RuntimeConfigurationType getConfigurationType() 829 { 830 if (configurationType == null) 831 { 832 String result = null; 833 try 834 { 835 result = System.getProperty("wicket." + Application.CONFIGURATION); 836 } 837 catch (SecurityException e) 838 { 839 log.warn("SecurityManager doesn't allow to read the configuration type from " + 840 "the system properties. The configuration type will be read from the web.xml."); 841 } 842 843 // If no system parameter check filter/servlet <init-param> and <context-param> 844 if (result == null) 845 { 846 result = getInitParameter("wicket." + Application.CONFIGURATION); 847 } 848 if (result == null) 849 { 850 result = getServletContext().getInitParameter("wicket." + Application.CONFIGURATION); 851 } 852 853 // If no system parameter check filter/servlet specific <init-param> 854 if (result == null) 855 { 856 result = getInitParameter(Application.CONFIGURATION); 857 } 858 859 // If no system parameter and no <init-param>, then check 860 // <context-param> 861 if (result == null) 862 { 863 result = getServletContext().getInitParameter(Application.CONFIGURATION); 864 } 865 866 // Return result if we have found it, else fall back to DEVELOPMENT mode 867 // as the default. 868 if (result != null) 869 { 870 try 871 { 872 configurationType = RuntimeConfigurationType.valueOf(result.toUpperCase(Locale.ROOT)); 873 } 874 catch (IllegalArgumentException e) 875 { 876 // Ignore : fall back to DEVELOPMENT mode 877 // log.warn("Unknown runtime configuration type '" + result + 878 // "', falling back to DEVELOPMENT mode."); 879 throw new IllegalArgumentException("Invalid configuration type: '" + result + 880 "'. Must be \"development\" or \"deployment\"."); 881 } 882 } 883 } 884 885 if (configurationType == null) 886 { 887 configurationType = RuntimeConfigurationType.DEVELOPMENT; 888 } 889 890 return configurationType; 891 } 892 893 /** 894 * The rules if and when to insert an xml decl in the response are a bit tricky. Hence, we allow 895 * the user to replace the default implementation per page and per application. 896 * <p> 897 * Default implementation: the page mime type must be "application/xhtml+xml" and request 898 * HTTP_ACCEPT header must include "application/xhtml+xml" to automatically include the xml 899 * decl. Please see <a href= 900 * "https://developer.mozilla.org/en/Writing_JavaScript_for_XHTML#Finally:_Content_Negotiation" 901 * >Writing JavaScript for XHTML</a> for details. 902 * <p> 903 * Please note that xml decls in Wicket's markup are only used for reading the markup. The 904 * markup's xml decl will always be removed and never be used to configure the response. 905 * 906 * @param page 907 * The page currently being rendered 908 * @param insert 909 * If false, than the rules are applied. If true, it'll always be written. In order 910 * to never insert it, than subclass renderXmlDecl() with an empty implementation. 911 */ 912 public void renderXmlDecl(final WebPage page, boolean insert) 913 { 914 if (insert || MarkupType.XML_MIME.equalsIgnoreCase(page.getMarkupType().getMimeType())) 915 { 916 final RequestCycle cycle = RequestCycle.get(); 917 918 if (insert == false) 919 { 920 WebRequest request = (WebRequest)cycle.getRequest(); 921 922 String accept = request.getHeader("Accept"); 923 insert = ((accept == null) || (accept.indexOf(MarkupType.XML_MIME) != -1)); 924 } 925 926 if (insert) 927 { 928 WebResponse response = (WebResponse)cycle.getResponse(); 929 response.write("<?xml version='1.0'"); 930 String encoding = getRequestCycleSettings().getResponseRequestEncoding(); 931 if (Strings.isEmpty(encoding) == false) 932 { 933 response.write(" encoding='"); 934 response.write(encoding); 935 response.write("'"); 936 } 937 response.write(" ?>"); 938 } 939 } 940 } 941 942 /** 943 * Creates a new ajax request target used to control ajax responses 944 * 945 * @param page 946 * page on which ajax response is made 947 * @return non-null ajax request target instance 948 */ 949 public final AjaxRequestTarget newAjaxRequestTarget(final Page page) 950 { 951 AjaxRequestTarget target = getAjaxRequestTargetProvider().apply(page); 952 for (AjaxRequestTarget.IListener listener : ajaxRequestTargetListeners) 953 { 954 target.addListener(listener); 955 } 956 return target; 957 } 958 959 /** 960 * Log that this application is started. 961 */ 962 final void logStarted() 963 { 964 if (log.isInfoEnabled()) 965 { 966 String version = getFrameworkSettings().getVersion(); 967 StringBuilder b = new StringBuilder(); 968 b.append("[").append(getName()).append("] Started Wicket "); 969 if (!"n/a".equals(version)) 970 { 971 b.append("version ").append(version).append(" "); 972 } 973 b.append("in ").append(getConfigurationType()).append(" mode"); 974 log.info(b.toString()); 975 } 976 977 if (usesDevelopmentConfig()) 978 { 979 outputDevelopmentModeWarning(); 980 } 981 } 982 983 /** 984 * This method prints a warning to stderr that we are starting in development mode. 985 * <p> 986 * If you really need to test Wicket in development mode on a staging server somewhere and are 987 * annoying the sysadmin for it with stderr messages, you can override this to make it do 988 * something else. 989 */ 990 protected void outputDevelopmentModeWarning() 991 { 992 System.err.print("********************************************************************\n" 993 + "*** WARNING: Wicket is running in DEVELOPMENT mode. ***\n" 994 + "*** ^^^^^^^^^^^ ***\n" 995 + "*** Do NOT deploy to your live server(s) without changing this. ***\n" 996 + "*** See Application#getConfigurationType() for more information. ***\n" 997 + "********************************************************************\n"); 998 } 999 1000 /* 1001 * Can contain at most 1000 responses and each entry can live at most one minute. For now there 1002 * is no need to configure these parameters externally. 1003 */ 1004 private final StoredResponsesMap storedResponses = new StoredResponsesMap(1000, 1005 Duration.ofSeconds(60)); 1006 1007 /** 1008 * 1009 * @param sessionId 1010 * @param url 1011 * @return true if has buffered response 1012 */ 1013 public boolean hasBufferedResponse(String sessionId, Url url) 1014 { 1015 String key = sessionId + url.toString(); 1016 return storedResponses.containsKey(key); 1017 } 1018 1019 /** 1020 * Retrieves a stored buffered response for a given sessionId and url. 1021 * 1022 * @param url 1023 * The url used as a key 1024 * @return the stored buffered response. {@code null} if there is no stored response for the given url 1025 * @see org.apache.wicket.settings.RequestCycleSettings.RenderStrategy#REDIRECT_TO_BUFFER 1026 */ 1027 public BufferedWebResponse getAndRemoveBufferedResponse(String sessionId, Url url) 1028 { 1029 String key = sessionId + url.toString(); 1030 return storedResponses.remove(key); 1031 } 1032 1033 /** 1034 * Store the buffered response at application level to use it at a later time. 1035 * 1036 * @param sessionId 1037 * @param url 1038 * @param response 1039 */ 1040 public void storeBufferedResponse(String sessionId, Url url, BufferedWebResponse response) 1041 { 1042 if (Strings.isEmpty(sessionId)) 1043 { 1044 log.error("storeBufferedResponse needs a valid session id to store the response, but a null one was found. " 1045 + "Please report the problem to dev team and try to reproduce it in a quickstart project."); 1046 return; 1047 } 1048 1049 String key = sessionId + url.toString(); 1050 storedResponses.put(key, response); 1051 } 1052 1053 @Override 1054 public String getMimeType(String fileName) 1055 { 1056 String mimeType = getServletContext().getMimeType(fileName); 1057 return mimeType != null ? mimeType : super.getMimeType(fileName); 1058 } 1059 1060 /** 1061 * Returns the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1062 * 1063 * @return the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1064 */ 1065 public Function<Page, AjaxRequestTarget> getAjaxRequestTargetProvider() 1066 { 1067 return ajaxRequestTargetProvider; 1068 } 1069 1070 /** 1071 * Sets the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1072 * 1073 * @param ajaxRequestTargetProvider 1074 * the new provider 1075 */ 1076 public Application setAjaxRequestTargetProvider( 1077 Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider) 1078 { 1079 this.ajaxRequestTargetProvider = ajaxRequestTargetProvider; 1080 return this; 1081 } 1082 1083 /** 1084 * Returns the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects. 1085 * 1086 * @return the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects. 1087 */ 1088 public AjaxRequestTargetListenerCollection getAjaxRequestTargetListeners() 1089 { 1090 return ajaxRequestTargetListeners; 1091 } 1092 1093 /** 1094 * @return True if at least one filter factory has been added. 1095 */ 1096 public final boolean hasFilterFactoryManager() 1097 { 1098 return filterFactoryManager != null; 1099 } 1100 1101 /** 1102 * @return The filter factory manager 1103 */ 1104 public final FilterFactoryManager getFilterFactoryManager() 1105 { 1106 if (filterFactoryManager == null) 1107 { 1108 filterFactoryManager = new FilterFactoryManager(); 1109 } 1110 return filterFactoryManager; 1111 } 1112 1113 /** 1114 * TODO remove in Wicket 10 1115 * 1116 * @deprecated use {@link #setCspSettings(ContentSecurityPolicySettings)} instead 1117 */ 1118 protected ContentSecurityPolicySettings newCspSettings() 1119 { 1120 return new ContentSecurityPolicySettings(this); 1121 } 1122 1123 /** 1124 * Returns the {@link ContentSecurityPolicySettings} for this application. See 1125 * {@link ContentSecurityPolicySettings} and {@link CSPHeaderConfiguration} for instructions on 1126 * configuring the CSP for your specific needs. 1127 * 1128 * @return The {@link ContentSecurityPolicySettings} for this application. 1129 * @see ContentSecurityPolicySettings 1130 * @see CSPHeaderConfiguration 1131 * 1132 * TODO make final in Wicket 10 1133 */ 1134 public ContentSecurityPolicySettings getCspSettings() 1135 { 1136 checkSettingsAvailable(); 1137 1138 if (cspSettings == null) 1139 { 1140 cspSettings = newCspSettings(); 1141 } 1142 return cspSettings; 1143 } 1144 1145 /** 1146 * Set CSP settings. 1147 * 1148 */ 1149 public void setCspSettings(ContentSecurityPolicySettings cspSettings) 1150 { 1151 this.cspSettings = cspSettings; 1152 } 1153}