|
1 | 1 | /******************************************************************************* |
2 | | - * Copyright (c) 2011, 2023 IBM Corporation and others. |
| 2 | + * Copyright (c) 2011, 2026 IBM Corporation and others. |
3 | 3 | * All rights reserved. This program and the accompanying materials |
4 | 4 | * are made available under the terms of the Eclipse Public License 2.0 |
5 | 5 | * which accompanies this distribution, and is available at |
|
16 | 16 | // 148426 10/06/14 bitonti Give extension default error page precedence in Servlet 3.0 |
17 | 17 | // PI67942 10/21/16 zaroman encode URI after dispatch |
18 | 18 | // 11909 12/11/20 jimblye Allow context-root to be overridden in server.xml |
| 19 | +// 02/06/26. jimblye Defer servlet mappings until webfragment servlet defs processed |
19 | 20 |
|
20 | 21 | package com.ibm.ws.webcontainer.osgi.container.config; |
21 | 22 |
|
|
156 | 157 | * from web.xml, web-fragment.xml and annotations. Configure them into the WebAppConfiguration. |
157 | 158 | */ |
158 | 159 | public class WebAppConfiguratorHelper implements ServletConfiguratorHelper { |
| 160 | + |
| 161 | + /** |
| 162 | + * Helper class to store servlet mappings for deferred processing. |
| 163 | + * This allows all servlet definitions to be collected before processing mappings. |
| 164 | + */ |
| 165 | + private static class DeferredServletMapping { |
| 166 | + final ServletMapping servletMapping; |
| 167 | + final ConfigSource source; |
| 168 | + final String libraryURI; |
| 169 | + |
| 170 | + DeferredServletMapping(ServletMapping servletMapping, ConfigSource source, String libraryURI) { |
| 171 | + this.servletMapping = servletMapping; |
| 172 | + this.source = source; |
| 173 | + this.libraryURI = libraryURI; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + private final List<DeferredServletMapping> deferredServletMappings = new ArrayList<>(); |
159 | 178 | private static final String CLASS_NAME = WebAppConfiguratorHelper.class.getSimpleName(); |
160 | 179 |
|
161 | 180 | private static final TraceComponent tc = Tr.register(WebAppConfiguratorHelper.class, WebContainerConstants.TR_GROUP, WebContainerConstants.NLS_PROPS); |
@@ -818,7 +837,9 @@ public void configureFromWebApp(WebApp webApp) throws UnableToAdaptException { |
818 | 837 |
|
819 | 838 | configureSessionConfig(webApp.getSessionConfig()); |
820 | 839 | configureServlets(webApp, webApp.getServlets()); |
821 | | - configureServletMappings(webApp.getServletMappings()); |
| 840 | + |
| 841 | + // Defer servlet mapping processing until all servlet definitions are collected |
| 842 | + storeDeferredServletMappings(webApp.getServletMappings()); |
822 | 843 | configureLocaleEncodingMap(webApp.getLocaleEncodingMappingList()); |
823 | 844 | configureListener(webApp.getListeners()); |
824 | 845 | configureEnvEntries(webApp.getEnvEntries()); |
@@ -856,7 +877,9 @@ public void configureFromWebFragment(WebFragmentInfo webFragmentItem) throws Una |
856 | 877 | configureDistributableFromFragment(webFragment.isSetDistributable()); |
857 | 878 | configureSessionConfig(webFragment.getSessionConfig()); |
858 | 879 | configureServlets(webFragment, webFragment.getServlets()); |
859 | | - configureServletMappings(webFragment.getServletMappings()); |
| 880 | + |
| 881 | + // Defer servlet mapping processing until all servlet definitions are collected |
| 882 | + storeDeferredServletMappings(webFragment.getServletMappings()); |
860 | 883 | configureLocaleEncodingMap(webFragment.getLocaleEncodingMappingList()); |
861 | 884 | configureListener(webFragment.getListeners()); |
862 | 885 | configureEnvEntries(webFragment.getEnvEntries()); |
@@ -913,7 +936,11 @@ public void configureFromAnnotations(WebFragmentInfo webFragmentItem) throws Una |
913 | 936 | @Override |
914 | 937 | public void configureDefaults() throws UnableToAdaptException { |
915 | 938 |
|
916 | | - //I think we need to process specifiedClasses first to find what servlets were defined |
| 939 | + // Process all deferred servlet mappings now that all servlet definitions have been collected |
| 940 | + // from web.xml and all web-fragment.xml files |
| 941 | + processDeferredServletMappings(); |
| 942 | + |
| 943 | + //I think we need to process specifiedClasses first to find what servlets were defined |
917 | 944 | //in case a filter is mapped to * (all servlets) |
918 | 945 | configureSpecifiedClasses(); |
919 | 946 |
|
@@ -2564,9 +2591,78 @@ private void configureServlets(DeploymentDescriptor webDD, List<Servlet> servlet |
2564 | 2591 | } |
2565 | 2592 | } |
2566 | 2593 |
|
2567 | | - private void configureServletMappings(List<ServletMapping> servletMappings) throws UnableToAdaptException { |
| 2594 | + /** |
| 2595 | + * Process all deferred servlet mappings after all servlet definitions have been collected. |
| 2596 | + * The deferred mappings retain their original source context for proper merging rules and error messages. |
| 2597 | + * |
| 2598 | + * @throws UnableToAdaptException if servlet mapping processing fails |
| 2599 | + */ |
| 2600 | + private void processDeferredServletMappings() throws UnableToAdaptException { |
| 2601 | + |
| 2602 | + // Save the current context |
| 2603 | + ConfigSource savedSource = configurator.getConfigSource(); |
| 2604 | + String savedLibraryURI = configurator.getLibraryURI(); |
| 2605 | + |
| 2606 | + for (DeferredServletMapping deferred : deferredServletMappings) { |
| 2607 | + try { |
| 2608 | + // Retrieve the context from for this servlet mapping |
| 2609 | + // Needed for proper merging rules in processServletMappingConfig(...) |
| 2610 | + configurator.setConfigSource(deferred.source); |
| 2611 | + configurator.setLibraryURI(deferred.libraryURI); |
| 2612 | + processServletMappingConfig(deferred.servletMapping); |
| 2613 | + } finally { |
| 2614 | + // Restore the saved context |
| 2615 | + configurator.setConfigSource(savedSource); |
| 2616 | + configurator.setLibraryURI(savedLibraryURI); |
| 2617 | + } |
| 2618 | + } |
| 2619 | + deferredServletMappings.clear(); |
| 2620 | + } |
| 2621 | + |
| 2622 | + /** |
| 2623 | + * Store servlet mappings for deferred processing. |
| 2624 | + * This allows all servlet definitions to be collected from web.xml and web-fragment.xml |
| 2625 | + * files before processing the mappings, which is necessary when a servlet-mapping in |
| 2626 | + * web.xml references a servlet whose definition is provided in a web-fragment.xml. |
| 2627 | + * |
| 2628 | + * <p>We only defer mappings for servlets that don't have a definition yet. |
| 2629 | + * If the servlet is already defined (e.g., from an earlier source like annotations annotations or web.xml), |
| 2630 | + * we process the mapping immediately. This |
| 2631 | + * <ul> |
| 2632 | + * <li>allows processServletMappingConfig() to apply the correct merge rules based on ConfigSource |
| 2633 | + * (web.xml > web-fragment.xml > annotations)</li> |
| 2634 | + * <li>prevents duplicate mapping attempts that would occur if we deferred all mappings and then |
| 2635 | + * tried to add them again after annotations have already added them</li> |
| 2636 | + * <li>maintains the existing precedence behavior where earlier sources override later ones</li> |
| 2637 | + * </ul> |
| 2638 | + * |
| 2639 | + * @param servletMappings the list of servlet mappings to process or defer |
| 2640 | + * @throws UnableToAdaptException if servlet mapping processing fails |
| 2641 | + */ |
| 2642 | + private void storeDeferredServletMappings(List<ServletMapping> servletMappings) throws UnableToAdaptException { |
| 2643 | + if (servletMappings == null || servletMappings.isEmpty()) { |
| 2644 | + return; |
| 2645 | + } |
| 2646 | + |
| 2647 | + ConfigSource currentSource = configurator.getConfigSource(); |
| 2648 | + String currentLibraryURI = configurator.getLibraryURI(); |
| 2649 | + |
2568 | 2650 | for (ServletMapping servletMapping : servletMappings) { |
2569 | | - processServletMappingConfig(servletMapping); |
| 2651 | + String servletName = servletMapping.getServletName(); |
| 2652 | + Map<String, ConfigItem<ServletConfig>> servletMap = configurator.getConfigItemMap("servlet"); |
| 2653 | + |
| 2654 | + // Check if servlet definition exists |
| 2655 | + if (servletMap.get(servletName) == null) { |
| 2656 | + // Servlet not defined yet - defer mapping until all servlet definitions are collected |
| 2657 | + // This handles the case where web.xml has a servlet-mapping but the servlet definition |
| 2658 | + // is in a web-fragment.xml that hasn't been processed yet |
| 2659 | + deferredServletMappings.add(new DeferredServletMapping(servletMapping, currentSource, currentLibraryURI)); |
| 2660 | + } else { |
| 2661 | + // Servlet already defined - process mapping immediately to maintain proper precedence |
| 2662 | + // This ensures processServletMappingConfig() can apply merge rules correctly and |
| 2663 | + // prevents duplicate mappings when annotations have already added mappings |
| 2664 | + processServletMappingConfig(servletMapping); |
| 2665 | + } |
2570 | 2666 | } |
2571 | 2667 | } |
2572 | 2668 |
|
|
0 commit comments