Compare commits

...

11 Commits

Author SHA1 Message Date
Spring Builds b40e8e047c Next development version (v5.2.26.BUILD-SNAPSHOT) 2023-07-13 07:51:55 +00:00
Sam Brannen a789e29a52 Add "/framework-docs/build" to .gitignore 2023-07-12 11:57:12 +02:00
Brian Clozel e6d5b385f6 Use docker credentials for fetching changelog image
This commit updates the CI pipeline to use the configured docker
credentials for fetching the changelog-generator CI image for the
relevant task.
2023-07-12 08:30:00 +02:00
Brian Clozel c206866fed Use concourse-release-script docker image in CI 2023-07-12 08:23:28 +02:00
Brian Clozel ca01ee05f7 Update JDK and OS versions in CI image 2023-07-12 08:22:34 +02:00
rstoyanchev 2e72269eb0 Update links to asciidoc resources
Essentially a backport of 572bbe and 7dae3a from 5.3.x
2023-07-11 14:00:17 +01:00
rstoyanchev ac82b5cd36 Encapsulate full path initialization 2023-07-11 12:17:47 +01:00
rstoyanchev 921635b3bc Add ignore rule for cached-antora-playbook.yml
In case of checking out the 5.3.x branch after 6.0.x or main
2023-05-23 15:15:42 +01:00
Sam Brannen 7c8fed7f3b Make maximum SpEL expression length configurable
Closes gh-30452
2023-05-10 15:57:55 +02:00
Sam Brannen 89a3d64ada Reintroduce support for null SpEL expressions
Closes gh-30464
2023-05-10 15:47:49 +02:00
Spring Builds a8bc09925d Next development version (v5.2.25.BUILD-SNAPSHOT) 2023-04-13 09:30:19 +00:00
23 changed files with 163 additions and 83 deletions
+3
View File
@@ -23,6 +23,7 @@ buildSrc/build
/spring-*/build
/spring-core/kotlin-coroutines/build
/framework-bom/build
/framework-docs/build
/integration-tests/build
/src/asciidoc/build
target/
@@ -41,3 +42,5 @@ out
test-output
atlassian-ide-plugin.xml
.gradletasknamecache
cached-antora-playbook.yml
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20220302
FROM ubuntu:jammy-20230624
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+2 -2
View File
@@ -3,10 +3,10 @@ set -e
case "$1" in
java8)
echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz"
;;
java11)
echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19%2B7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
-3
View File
@@ -277,7 +277,6 @@ jobs:
download_artifacts: false
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: M
@@ -322,7 +321,6 @@ jobs:
download_artifacts: false
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RC
@@ -367,7 +365,6 @@ jobs:
download_artifacts: true
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RELEASE
+2 -2
View File
@@ -6,11 +6,11 @@ CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /opt/concourse-release-scripts.jar \
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
java -jar /opt/concourse-release-scripts.jar \
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
+3 -1
View File
@@ -4,7 +4,9 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.7'
tag: '0.0.8'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
+7
View File
@@ -1,5 +1,12 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.3.4'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.2.24.BUILD-SNAPSHOT
version=5.2.26.BUILD-SNAPSHOT
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+3 -4
View File
@@ -3,7 +3,7 @@ configurations {
}
dependencies {
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.0.RELEASE")
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.1")
}
repositories {
@@ -107,9 +107,8 @@ dokka {
}
task downloadResources(type: Download) {
def version = "0.2.1.RELEASE"
src "https://repo.spring.io/release/io/spring/docresources/" +
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
src "https://repo.spring.io/artifactory/snapshot/io/spring/docresources/" +
"spring-doc-resources/0.2.6-SNAPSHOT/spring-doc-resources-0.2.6-20210308.231804-2.zip"
dest project.file("$buildDir/docs/spring-doc-resources.zip")
onlyIfModified true
useETag "all"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,11 +25,19 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller
* @author Phillip Webb
* @author Andy Clement
* @author Sam Brannen
* @since 3.0
* @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration)
*/
public class SpelParserConfiguration {
/**
* Default maximum length permitted for a SpEL expression.
* @since 5.2.24
*/
private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000;
private static final SpelCompilerMode defaultCompilerMode;
static {
@@ -50,6 +58,8 @@ public class SpelParserConfiguration {
private final int maximumAutoGrowSize;
private final int maximumExpressionLength;
/**
* Create a new {@code SpelParserConfiguration} instance with default settings.
@@ -98,11 +108,30 @@ public class SpelParserConfiguration {
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections,
maximumAutoGrowSize, DEFAULT_MAX_EXPRESSION_LENGTH);
}
/**
* Create a new {@code SpelParserConfiguration} instance.
* @param compilerMode the compiler mode that parsers using this configuration object should use
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size that a collection can auto grow
* @param maximumExpressionLength the maximum length of a SpEL expression;
* must be a positive number
* @since 5.2.25
*/
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) {
this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode);
this.compilerClassLoader = compilerClassLoader;
this.autoGrowNullReferences = autoGrowNullReferences;
this.autoGrowCollections = autoGrowCollections;
this.maximumAutoGrowSize = maximumAutoGrowSize;
this.maximumExpressionLength = maximumExpressionLength;
}
@@ -142,4 +171,12 @@ public class SpelParserConfiguration {
return this.maximumAutoGrowSize;
}
/**
* Return the maximum number of characters that a SpEL expression can contain.
* @since 5.2.25
*/
public int getMaximumExpressionLength() {
return this.maximumExpressionLength;
}
}
@@ -93,13 +93,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+");
/**
* Maximum length permitted for a SpEL expression.
* @since 5.2.24
*/
private static final int MAX_EXPRESSION_LENGTH = 10_000;
private final SpelParserConfiguration configuration;
// For rules that build nodes, they are stacked here for return
@@ -158,8 +151,11 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
private void checkExpressionLength(String string) {
if (string.length() > MAX_EXPRESSION_LENGTH) {
throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, MAX_EXPRESSION_LENGTH);
if (string != null) {
int maxLength = this.configuration.getMaximumExpressionLength();
if (string.length() > maxLength) {
throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, maxLength);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -164,6 +164,24 @@ public abstract class AbstractExpressionTests {
*/
protected void evaluateAndCheckError(String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
Object... otherProperties) {
evaluateAndCheckError(this.parser, expression, expectedReturnType, expectedMessage, otherProperties);
}
/**
* Evaluate the specified expression and ensure the expected message comes out.
* The message may have inserts and they will be checked if otherProperties is specified.
* The first entry in otherProperties should always be the position.
* @param parser the expression parser to use
* @param expression the expression to evaluate
* @param expectedReturnType ask the expression return value to be of this type if possible
* ({@code null} indicates don't ask for conversion)
* @param expectedMessage the expected message
* @param otherProperties the expected inserts within the message
*/
protected void evaluateAndCheckError(ExpressionParser parser, String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
Object... otherProperties) {
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> {
Expression expr = parser.parseExpression(expression);
assertThat(expr).as("expression").isNotNull();
@@ -76,6 +76,26 @@ public class EvaluationTests extends AbstractExpressionTests {
evaluateAndCheckError(expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED);
}
@Test
void maxExpressionLengthIsConfigurable() {
int maximumExpressionLength = 20_000;
String expression = String.format("'%s'", repeat("Y", 19_998));
assertThat(expression).hasSize(maximumExpressionLength);
SpelParserConfiguration configuration =
new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength);
ExpressionParser parser = new SpelExpressionParser(configuration);
Expression expr = parser.parseExpression(expression);
String result = expr.getValue(String.class);
assertThat(result).hasSize(19_998);
expression = String.format("'%s'", repeat("Y", 25_000));
assertThat(expression).hasSize(25_002);
evaluateAndCheckError(parser, expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED);
}
@Test
public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,9 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.SpelParseException;
@@ -35,9 +37,19 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andy Clement
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class SpelParserTests {
@Test // gh-30464
public void nullExpression() {
ExpressionParser parser = new SpelExpressionParser();
String expression = null;
Expression expr = parser.parseExpression(expression);
Object result = expr.getValue();
assertThat(result).isNull();
}
@Test
public void theMostBasic() {
SpelExpressionParser parser = new SpelExpressionParser();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.server.PathContainer;
import org.springframework.util.StringUtils;
/**
* Parser for URI path patterns producing {@link PathPattern} instances that can
@@ -103,6 +104,17 @@ public class PathPatternParser {
}
/**
* Prepare the given pattern for use in matching to full URL paths.
* <p>By default, prepend a leading slash if needed for non-empty patterns.
* @param pattern the pattern to initialize
* @return the updated pattern
* @since 5.2.25
*/
public String initFullPathPattern(String pattern) {
return (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
}
/**
* Process the path pattern content, a character at a time, breaking it into
* path elements around separator boundaries and verifying the structure at each
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -109,10 +109,9 @@ public abstract class RequestPredicates {
*/
public static RequestPredicate path(String pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
PathPatternParser parser = PathPatternParser.defaultInstance;
pattern = parser.initFullPathPattern(pattern);
return pathPredicates(parser).apply(pattern);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,9 +28,9 @@ import org.springframework.beans.BeansException;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Abstract base class for URL-mapped
@@ -185,8 +185,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
Object resolvedHandler = handler;
// Parse path pattern
urlPath = prependLeadingSlash(urlPath);
PathPattern pattern = getPathPatternParser().parse(urlPath);
PathPatternParser parser = getPathPatternParser();
urlPath = parser.initFullPathPattern(urlPath);
PathPattern pattern = parser.parse(urlPath);
if (this.handlerMap.containsKey(pattern)) {
Object existingHandler = this.handlerMap.get(pattern);
if (existingHandler != null && existingHandler != resolvedHandler) {
@@ -215,14 +216,4 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
}
private static String prependLeadingSlash(String pattern) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
return "/" + pattern;
}
else {
return pattern;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
@@ -86,8 +85,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
public void registerHandlers(Map<String, ResourceWebHandler> handlerMap) {
this.handlerMap.clear();
handlerMap.forEach((rawPattern, resourceWebHandler) -> {
rawPattern = prependLeadingSlash(rawPattern);
PathPattern pattern = PathPatternParser.defaultInstance.parse(rawPattern);
PathPatternParser parser = PathPatternParser.defaultInstance;
rawPattern = parser.initFullPathPattern(rawPattern);
PathPattern pattern = parser.parse(rawPattern);
this.handlerMap.put(pattern, resourceWebHandler);
});
}
@@ -173,14 +173,4 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
});
}
private static String prependLeadingSlash(String pattern) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
return "/" + pattern;
}
else {
return pattern;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -564,11 +564,9 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
return Collections.emptyList();
}
List<PathPattern> result = new ArrayList<>(patterns.length);
for (String path : patterns) {
if (StringUtils.hasText(path) && !path.startsWith("/")) {
path = "/" + path;
}
result.add(parser.parse(path));
for (String pattern : patterns) {
pattern = parser.initFullPathPattern(pattern);
result.add(parser.parse(pattern));
}
return result;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,10 +107,9 @@ public abstract class RequestPredicates {
*/
public static RequestPredicate path(String pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
PathPatternParser parser = PathPatternParser.defaultInstance;
pattern = parser.initFullPathPattern(pattern);
return pathPredicates(parser).apply(pattern);
}
/**
@@ -333,14 +332,14 @@ public abstract class RequestPredicates {
void method(Set<HttpMethod> methods);
/**
* Receive notification of an path predicate.
* Receive notification of a path predicate.
* @param pattern the path pattern that makes up the predicate
* @see RequestPredicates#path(String)
*/
void path(String pattern);
/**
* Receive notification of an path extension predicate.
* Receive notification of a path extension predicate.
* @param extension the path extension that makes up the predicate
* @see RequestPredicates#pathExtension(String)
*/
@@ -426,11 +425,11 @@ public abstract class RequestPredicates {
void unknown(RequestPredicate predicate);
}
private static class HttpMethodPredicate implements RequestPredicate {
private final Set<HttpMethod> httpMethods;
public HttpMethodPredicate(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "HttpMethod must not be null");
this.httpMethods = EnumSet.of(httpMethod);
@@ -641,12 +640,14 @@ public abstract class RequestPredicates {
}
}
private static class PathExtensionPredicate implements RequestPredicate {
private final Predicate<String> extensionPredicate;
@Nullable
private final String extension;
public PathExtensionPredicate(Predicate<String> extensionPredicate) {
Assert.notNull(extensionPredicate, "Predicate must not be null");
this.extensionPredicate = extensionPredicate;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* A logical disjunction (' || ') request condition that matches a request
@@ -142,9 +143,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
}
Set<String> result = new LinkedHashSet<>(patterns.length);
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
pattern = PathPatternParser.defaultInstance.initFullPathPattern(pattern);
result.add(pattern);
}
return result;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* Creates instances of {@link org.springframework.web.util.UriComponentsBuilder}
@@ -545,9 +546,7 @@ public class MvcUriComponentsBuilder {
String typePath = getClassMapping(controllerType);
String methodPath = getMethodMapping(method);
String path = pathMatcher.combine(typePath, methodPath);
if (StringUtils.hasLength(path) && !path.startsWith("/")) {
path = "/" + path;
}
path = PathPatternParser.defaultInstance.initFullPathPattern(path);
builder.path(path);
return applyContributors(builder, method, args);