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 AjaxRequestTargetListenerCollection ajaxRequestTargetListeners = getAjaxRequestTargetListeners(); 762 ajaxRequestTargetListeners.add(new AjaxEnclosureListener()); 763 ajaxRequestTargetListeners.add(new MultipartFormComponentListener()); 764 765 // Configure the app. 766 configure(); 767 if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) 768 { 769 // Add optional sourceFolder for resources. 770 String resourceFolder = getInitParameter("sourceFolder"); 771 if (resourceFolder != null) 772 { 773 getResourceSettings().getResourceFinders().add(new Path(resourceFolder)); 774 } 775 getCspSettings().blocking().reportBack(); 776 } 777 getCspSettings().blocking().strict(); 778 } 779 780 @Override 781 protected void validateInit() 782 { 783 super.validateInit(); 784 785 if (getCspSettings().isEnabled()) { 786 getCspSettings().enforce(this); 787 } 788 789 // enable coop and coep listeners if specified in security settings 790 CrossOriginOpenerPolicyConfiguration coopConfig = getSecuritySettings() 791 .getCrossOriginOpenerPolicyConfiguration(); 792 if (coopConfig.isEnabled()) 793 { 794 getRequestCycleListeners() 795 .add(new CrossOriginOpenerPolicyRequestCycleListener(coopConfig)); 796 } 797 798 CrossOriginEmbedderPolicyConfiguration coepConfig = getSecuritySettings() 799 .getCrossOriginEmbedderPolicyConfiguration(); 800 if (coepConfig.isEnabled()) 801 { 802 getRequestCycleListeners() 803 .add(new CrossOriginEmbedderPolicyRequestCycleListener(coepConfig)); 804 } 805 } 806 807 /** 808 * set runtime configuration type 809 * <p/> 810 * this is a write-once property: once configured it can not be changed later on. 811 * 812 * @param configurationType 813 */ 814 public Application setConfigurationType(RuntimeConfigurationType configurationType) 815 { 816 if (this.configurationType != null) 817 { 818 throw new IllegalStateException( 819 "Configuration type is write-once. You can not change it. " + "" + 820 "Current value='" + configurationType + "'"); 821 } 822 this.configurationType = Args.notNull(configurationType, "configurationType"); 823 return this; 824 } 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override 830 public RuntimeConfigurationType getConfigurationType() 831 { 832 if (configurationType == null) 833 { 834 String result = null; 835 try 836 { 837 result = System.getProperty("wicket." + Application.CONFIGURATION); 838 } 839 catch (SecurityException e) 840 { 841 log.warn("SecurityManager doesn't allow to read the configuration type from " + 842 "the system properties. The configuration type will be read from the web.xml."); 843 } 844 845 // If no system parameter check filter/servlet <init-param> and <context-param> 846 if (result == null) 847 { 848 result = getInitParameter("wicket." + Application.CONFIGURATION); 849 } 850 if (result == null) 851 { 852 result = getServletContext().getInitParameter("wicket." + Application.CONFIGURATION); 853 } 854 855 // If no system parameter check filter/servlet specific <init-param> 856 if (result == null) 857 { 858 result = getInitParameter(Application.CONFIGURATION); 859 } 860 861 // If no system parameter and no <init-param>, then check 862 // <context-param> 863 if (result == null) 864 { 865 result = getServletContext().getInitParameter(Application.CONFIGURATION); 866 } 867 868 // Return result if we have found it, else fall back to DEVELOPMENT mode 869 // as the default. 870 if (result != null) 871 { 872 try 873 { 874 configurationType = RuntimeConfigurationType.valueOf(result.toUpperCase(Locale.ROOT)); 875 } 876 catch (IllegalArgumentException e) 877 { 878 // Ignore : fall back to DEVELOPMENT mode 879 // log.warn("Unknown runtime configuration type '" + result + 880 // "', falling back to DEVELOPMENT mode."); 881 throw new IllegalArgumentException("Invalid configuration type: '" + result + 882 "'. Must be \"development\" or \"deployment\"."); 883 } 884 } 885 } 886 887 if (configurationType == null) 888 { 889 configurationType = RuntimeConfigurationType.DEVELOPMENT; 890 } 891 892 return configurationType; 893 } 894 895 /** 896 * The rules if and when to insert an xml decl in the response are a bit tricky. Hence, we allow 897 * the user to replace the default implementation per page and per application. 898 * <p> 899 * Default implementation: the page mime type must be "application/xhtml+xml" and request 900 * HTTP_ACCEPT header must include "application/xhtml+xml" to automatically include the xml 901 * decl. Please see <a href= 902 * "https://developer.mozilla.org/en/Writing_JavaScript_for_XHTML#Finally:_Content_Negotiation" 903 * >Writing JavaScript for XHTML</a> for details. 904 * <p> 905 * Please note that xml decls in Wicket's markup are only used for reading the markup. The 906 * markup's xml decl will always be removed and never be used to configure the response. 907 * 908 * @param page 909 * The page currently being rendered 910 * @param insert 911 * If false, than the rules are applied. If true, it'll always be written. In order 912 * to never insert it, than subclass renderXmlDecl() with an empty implementation. 913 */ 914 public void renderXmlDecl(final WebPage page, boolean insert) 915 { 916 if (insert || MarkupType.XML_MIME.equalsIgnoreCase(page.getMarkupType().getMimeType())) 917 { 918 final RequestCycle cycle = RequestCycle.get(); 919 920 if (insert == false) 921 { 922 WebRequest request = (WebRequest)cycle.getRequest(); 923 924 String accept = request.getHeader("Accept"); 925 insert = ((accept == null) || (accept.indexOf(MarkupType.XML_MIME) != -1)); 926 } 927 928 if (insert) 929 { 930 WebResponse response = (WebResponse)cycle.getResponse(); 931 response.write("<?xml version='1.0'"); 932 String encoding = getRequestCycleSettings().getResponseRequestEncoding(); 933 if (Strings.isEmpty(encoding) == false) 934 { 935 response.write(" encoding='"); 936 response.write(encoding); 937 response.write("'"); 938 } 939 response.write(" ?>"); 940 } 941 } 942 } 943 944 /** 945 * Creates a new ajax request target used to control ajax responses 946 * 947 * @param page 948 * page on which ajax response is made 949 * @return non-null ajax request target instance 950 */ 951 public final AjaxRequestTarget newAjaxRequestTarget(final Page page) 952 { 953 AjaxRequestTarget target = getAjaxRequestTargetProvider().apply(page); 954 for (AjaxRequestTarget.IListener listener : ajaxRequestTargetListeners) 955 { 956 target.addListener(listener); 957 } 958 return target; 959 } 960 961 /** 962 * Log that this application is started. 963 */ 964 final void logStarted() 965 { 966 if (log.isInfoEnabled()) 967 { 968 String version = getFrameworkSettings().getVersion(); 969 StringBuilder b = new StringBuilder(); 970 b.append("[").append(getName()).append("] Started Wicket "); 971 if (!"n/a".equals(version)) 972 { 973 b.append("version ").append(version).append(" "); 974 } 975 b.append("in ").append(getConfigurationType()).append(" mode"); 976 log.info(b.toString()); 977 } 978 979 if (usesDevelopmentConfig()) 980 { 981 outputDevelopmentModeWarning(); 982 } 983 } 984 985 /** 986 * This method prints a warning to stderr that we are starting in development mode. 987 * <p> 988 * If you really need to test Wicket in development mode on a staging server somewhere and are 989 * annoying the sysadmin for it with stderr messages, you can override this to make it do 990 * something else. 991 */ 992 protected void outputDevelopmentModeWarning() 993 { 994 System.err.print("********************************************************************\n" 995 + "*** WARNING: Wicket is running in DEVELOPMENT mode. ***\n" 996 + "*** ^^^^^^^^^^^ ***\n" 997 + "*** Do NOT deploy to your live server(s) without changing this. ***\n" 998 + "*** See Application#getConfigurationType() for more information. ***\n" 999 + "********************************************************************\n"); 1000 } 1001 1002 /* 1003 * Can contain at most 1000 responses and each entry can live at most one minute. For now there 1004 * is no need to configure these parameters externally. 1005 */ 1006 private final StoredResponsesMap storedResponses = new StoredResponsesMap(1000, 1007 Duration.ofSeconds(60)); 1008 1009 /** 1010 * 1011 * @param sessionId 1012 * @param url 1013 * @return true if has buffered response 1014 */ 1015 public boolean hasBufferedResponse(String sessionId, Url url) 1016 { 1017 String key = sessionId + url.toString(); 1018 return storedResponses.containsKey(key); 1019 } 1020 1021 /** 1022 * Retrieves a stored buffered response for a given sessionId and url. 1023 * 1024 * @param url 1025 * The url used as a key 1026 * @return the stored buffered response. {@code null} if there is no stored response for the given url 1027 * @see org.apache.wicket.settings.RequestCycleSettings.RenderStrategy#REDIRECT_TO_BUFFER 1028 */ 1029 public BufferedWebResponse getAndRemoveBufferedResponse(String sessionId, Url url) 1030 { 1031 String key = sessionId + url.toString(); 1032 return storedResponses.remove(key); 1033 } 1034 1035 /** 1036 * Store the buffered response at application level to use it at a later time. 1037 * 1038 * @param sessionId 1039 * @param url 1040 * @param response 1041 */ 1042 public void storeBufferedResponse(String sessionId, Url url, BufferedWebResponse response) 1043 { 1044 if (Strings.isEmpty(sessionId)) 1045 { 1046 log.error("storeBufferedResponse needs a valid session id to store the response, but a null one was found. " 1047 + "Please report the problem to dev team and try to reproduce it in a quickstart project."); 1048 return; 1049 } 1050 1051 String key = sessionId + url.toString(); 1052 storedResponses.put(key, response); 1053 } 1054 1055 @Override 1056 public String getMimeType(String fileName) 1057 { 1058 String mimeType = getServletContext().getMimeType(fileName); 1059 return mimeType != null ? mimeType : super.getMimeType(fileName); 1060 } 1061 1062 /** 1063 * Returns the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1064 * 1065 * @return the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1066 */ 1067 public Function<Page, AjaxRequestTarget> getAjaxRequestTargetProvider() 1068 { 1069 return ajaxRequestTargetProvider; 1070 } 1071 1072 /** 1073 * Sets the provider for {@link org.apache.wicket.ajax.AjaxRequestTarget} objects. 1074 * 1075 * @param ajaxRequestTargetProvider 1076 * the new provider 1077 */ 1078 public Application setAjaxRequestTargetProvider( 1079 Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider) 1080 { 1081 this.ajaxRequestTargetProvider = ajaxRequestTargetProvider; 1082 return this; 1083 } 1084 1085 /** 1086 * Returns the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects. 1087 * 1088 * @return the registered {@link org.apache.wicket.ajax.AjaxRequestTarget.IListener} objects. 1089 */ 1090 public AjaxRequestTargetListenerCollection getAjaxRequestTargetListeners() 1091 { 1092 return ajaxRequestTargetListeners; 1093 } 1094 1095 /** 1096 * @return True if at least one filter factory has been added. 1097 */ 1098 public final boolean hasFilterFactoryManager() 1099 { 1100 return filterFactoryManager != null; 1101 } 1102 1103 /** 1104 * @return The filter factory manager 1105 */ 1106 public final FilterFactoryManager getFilterFactoryManager() 1107 { 1108 if (filterFactoryManager == null) 1109 { 1110 filterFactoryManager = new FilterFactoryManager(); 1111 } 1112 return filterFactoryManager; 1113 } 1114 1115 /** 1116 * TODO remove in Wicket 10 1117 * 1118 * @deprecated use {@link #setCspSettings(ContentSecurityPolicySettings)} instead 1119 */ 1120 protected ContentSecurityPolicySettings newCspSettings() 1121 { 1122 return new ContentSecurityPolicySettings(this); 1123 } 1124 1125 /** 1126 * Returns the {@link ContentSecurityPolicySettings} for this application. See 1127 * {@link ContentSecurityPolicySettings} and {@link CSPHeaderConfiguration} for instructions on 1128 * configuring the CSP for your specific needs. 1129 * 1130 * @return The {@link ContentSecurityPolicySettings} for this application. 1131 * @see ContentSecurityPolicySettings 1132 * @see CSPHeaderConfiguration 1133 * 1134 * TODO make final in Wicket 10 1135 */ 1136 public ContentSecurityPolicySettings getCspSettings() 1137 { 1138 checkSettingsAvailable(); 1139 1140 if (cspSettings == null) 1141 { 1142 cspSettings = newCspSettings(); 1143 } 1144 return cspSettings; 1145 } 1146 1147 /** 1148 * Set CSP settings. 1149 * 1150 */ 1151 public void setCspSettings(ContentSecurityPolicySettings cspSettings) 1152 { 1153 this.cspSettings = cspSettings; 1154 } 1155}