This is an automated email from the ASF dual-hosted git repository. jamesfredley pushed a commit to branch feat/url-mapping-precompute-seed in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 97b5cc9ec5549c604e4888e1b665aa70dbd807e0 Author: James Fredley <[email protected]> AuthorDate: Fri Jul 10 12:57:45 2026 -0400 Seed URL mappings index with runtime fallback Add UrlMappingsIndexProperties and load path with fallback to runtime UrlMappingsHolder behavior. Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding] --- grails-doc/src/en/guide/toc.yml | 1 + .../en/guide/upgrading/urlMappingsPrecompute.adoc | 7 ++ .../web/mapping/DefaultUrlMappingsHolder.java | 9 +++ .../web/mapping/UrlMappingsIndexProperties.java | 80 ++++++++++++++++++++++ .../mapping/UrlMappingsIndexPropertiesSpec.groovy | 35 ++++++++++ 5 files changed, 132 insertions(+) diff --git a/grails-doc/src/en/guide/toc.yml b/grails-doc/src/en/guide/toc.yml index b0e4a5c3ff..c3bb13effa 100644 --- a/grails-doc/src/en/guide/toc.yml +++ b/grails-doc/src/en/guide/toc.yml @@ -37,6 +37,7 @@ gettingStarted: upgrading: title: Upgrading from the previous versions upgrading80x: Upgrading from Grails 7 to Grails 8 + urlMappingsPrecompute: URL mapping precomputation seed upgrading72x: Upgrading from Grails 7.1 to Grails 7.2 upgrading71x: Upgrading from Grails 7.0 to Grails 7.1 upgrading70x: Upgrading from Grails 6 to Grails 7.0 diff --git a/grails-doc/src/en/guide/upgrading/urlMappingsPrecompute.adoc b/grails-doc/src/en/guide/upgrading/urlMappingsPrecompute.adoc new file mode 100644 index 0000000000..33c4462e87 --- /dev/null +++ b/grails-doc/src/en/guide/upgrading/urlMappingsPrecompute.adoc @@ -0,0 +1,7 @@ +=== URL mapping precomputation seed + +Grails 8.1 starts reserving `META-INF/grails/url-mappings-index.properties` as the build-time URL mappings index location. +The runtime `DefaultUrlMappingsHolder` now detects that descriptor and keeps the existing runtime mapping evaluation path as the fallback when no descriptor is packaged. + +This is a first compatibility slice only. +Applications do not need to generate the descriptor yet, and existing `UrlMappings.groovy` behavior remains authoritative until a later release wires a complete build-time index generator. diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java index 6d0ff5f437..ad912f2f81 100644 --- a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/DefaultUrlMappingsHolder.java @@ -101,6 +101,7 @@ public class DefaultUrlMappingsHolder implements UrlMappings { private final Set<String> DEFAULT_ACTION_PARAMS = CollectionUtils.newSet(UrlMapping.ACTION); private final PathMatcher pathMatcher = new AntPathMatcher(); private final AtomicInteger initCounter = new AtomicInteger(); + private final UrlMappingsIndexProperties precomputedIndexProperties; public DefaultUrlMappingsHolder(List<UrlMapping> mappings) { this(mappings, null, false); @@ -113,6 +114,7 @@ public class DefaultUrlMappingsHolder implements UrlMappings { public DefaultUrlMappingsHolder(List<UrlMapping> mappings, List excludePatterns, boolean doNotCallInit) { urlMappings = mappings; this.excludePatterns = excludePatterns; + this.precomputedIndexProperties = UrlMappingsIndexProperties.load(DefaultUrlMappingsHolder.class.getClassLoader()); if (!doNotCallInit) { initialize(); } @@ -132,6 +134,9 @@ public class DefaultUrlMappingsHolder implements UrlMappings { public void initialize() { sortMappings(); + if (precomputedIndexProperties.isPresent() && LOG.isDebugEnabled()) { + LOG.debug("Discovered " + UrlMappingsIndexProperties.LOCATION + "; runtime URL mapping fallback remains active"); + } cachedMatches = Caffeine.newBuilder() .maximumSize(maxWeightedCacheCapacity) @@ -233,6 +238,10 @@ public class DefaultUrlMappingsHolder implements UrlMappings { return excludePatterns; } + public UrlMappingsIndexProperties getPrecomputedIndexProperties() { + return precomputedIndexProperties; + } + public UrlCreator getReverseMapping(final String controller, final String action, Map params) { return getReverseMapping(controller, action, null, null, params); } diff --git a/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/UrlMappingsIndexProperties.java b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/UrlMappingsIndexProperties.java new file mode 100644 index 0000000000..318be2ca39 --- /dev/null +++ b/grails-web-url-mappings/src/main/groovy/org/grails/web/mapping/UrlMappingsIndexProperties.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.web.mapping; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Properties; + +/** + * Descriptor for a future build-time URL mappings index. + * + * @since 8.1 + */ +public final class UrlMappingsIndexProperties { + + public static final String LOCATION = "META-INF/grails/url-mappings-index.properties"; + + private static final UrlMappingsIndexProperties EMPTY = new UrlMappingsIndexProperties(false, new Properties()); + + private final boolean present; + private final Properties properties; + + private UrlMappingsIndexProperties(boolean present, Properties properties) { + this.present = present; + this.properties = properties; + } + + public static UrlMappingsIndexProperties load(ClassLoader classLoader) { + ClassLoader loader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader; + if (loader == null) { + return EMPTY; + } + try (InputStream inputStream = loader.getResourceAsStream(LOCATION)) { + if (inputStream == null) { + return EMPTY; + } + Properties properties = new Properties(); + properties.load(inputStream); + return new UrlMappingsIndexProperties(true, properties); + } + catch (IOException e) { + throw new IllegalStateException("Unable to load " + LOCATION, e); + } + } + + public boolean isPresent() { + return present; + } + + public Properties asProperties() { + Properties copy = new Properties(); + copy.putAll(properties); + return copy; + } + + public String getProperty(String name) { + return properties.getProperty(name); + } + + public Iterable<String> propertyNames() { + return present ? properties.stringPropertyNames() : Collections.emptySet(); + } +} diff --git a/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/UrlMappingsIndexPropertiesSpec.groovy b/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/UrlMappingsIndexPropertiesSpec.groovy new file mode 100644 index 0000000000..6ce2015112 --- /dev/null +++ b/grails-web-url-mappings/src/test/groovy/org/grails/web/mapping/UrlMappingsIndexPropertiesSpec.groovy @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.web.mapping + +import grails.web.mapping.UrlMapping +import spock.lang.Specification + +class UrlMappingsIndexPropertiesSpec extends Specification { + + void 'missing build-time URL mapping index keeps runtime fallback active'() { + when: + DefaultUrlMappingsHolder holder = new DefaultUrlMappingsHolder([] as List<UrlMapping>) + + then: + !holder.precomputedIndexProperties.present + holder.urlMappings.length == 0 + holder.matchAll('/books').length == 0 + } +}
