Skip to content

Commit e37add5

Browse files
committed
Add @DynamicPortUrl
Closes gh-34
1 parent e9b3d5d commit e37add5

File tree

5 files changed

+148
-6
lines changed

5 files changed

+148
-6
lines changed

README.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ static CommonsExecWebServerFactoryBean oauthServer() {
195195

196196
This is a list of well known composed `@DynamicProperty` annotations.
197197

198+
==== @DynamicPortUrl
199+
200+
This provides a simple way of mapping a property to a URL with a dynamic port that is expressed as the port property on the Bean that is created.
201+
The value is calculated as `http://{host}:{port}{contextRoot}`.
202+
203+
* name - the property name to use
204+
* host - the host to use (default is `localhost`)
205+
* port - a valid SpEL expression that determines the port to use for the URL (default port)
206+
* contextRoot - the context root to use (default is empty String)
207+
198208
==== @OAuth2ClientProviderIssuerUri
199209

200210
This provides a mapping to issuer-uri of https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.security.spring.security.oauth2.client.provider[the OAuth provider details].
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.experimental.boot.test.context;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Simplifies setting a property to a URL that contains a dynamic port obtained from a
27+
* property (default is named port) on the Bean. For example, assuming that the "messages"
28+
* Bean has a property named port that returns "1234", the following will add a property
29+
* named "messages.url" with a value of "http://localhost:1234".
30+
*
31+
* <code>
32+
* &#64;Bean
33+
* &#64;DynamicPortUrl(name = "messages.url")
34+
* CommonsExecWebServerFactoryBean messages() {
35+
* return CommonsExecWebServerFactoryBean.builder()
36+
* .classpath((cp) -> cp
37+
* .files("build/libs/messages-0.0.1-SNAPSHOT.jar")
38+
* );
39+
* }
40+
* </code>
41+
*
42+
* The following allows overrides the host property and context root to produce a property
43+
* named "messages.url" with a value of "http://127.0.0.1:1234/messages" (assuming the
44+
* port property returns "1234").
45+
*
46+
* <code>
47+
* &#64;Bean
48+
* &#64;DynamicPortUrl(name = "messages.url", host = "127.0.0.1", contextRoot = "/messages")
49+
* CommonsExecWebServerFactoryBean messages() {
50+
* return CommonsExecWebServerFactoryBean.builder()
51+
* .classpath((cp) -> cp
52+
* .files("build/libs/messages-0.0.1-SNAPSHOT.jar")
53+
* );
54+
* }
55+
* </code>
56+
*
57+
* @author Rob Winch
58+
*/
59+
@Target({ ElementType.METHOD, ElementType.TYPE })
60+
@Retention(RetentionPolicy.RUNTIME)
61+
@Documented
62+
@DynamicProperty(name = "${name}", value = "'http://${host}:' + port + '${contextRoot}'")
63+
public @interface DynamicPortUrl {
64+
65+
/**
66+
* The property name to use.
67+
* @return the property name to use
68+
*/
69+
String name();
70+
71+
/**
72+
* The host to use (default "localhost")
73+
* @return the host to use
74+
*/
75+
String host() default "localhost";
76+
77+
/**
78+
* The valid SpEL expression that determines the port to use for the URL (default
79+
* port).
80+
* @return the valid SpEL expression that determines the port to use for the URL
81+
*/
82+
String port() default "port";
83+
84+
/**
85+
* Specifies the context root for the URL (e.g. "/messages"). The default is an empty
86+
* String.
87+
* @return the context root for the URL
88+
*/
89+
String contextRoot() default "";
90+
91+
}

spring-boot-testjars/src/main/java/org/springframework/experimental/boot/test/context/DynamicPropertyRegistryPropertyFactory.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ DynamicPropertyRegistryProperty createRegistryProperty(MergedAnnotation<DynamicP
4545
return null;
4646
}
4747
MutablePropertySources propertySources = new MutablePropertySources();
48-
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
49-
Map<String, Object> annotationProperties = (metaSource != null) ? metaSource.asMap() : new HashMap<>();
48+
Map<String, Object> annotationProperties = collectAttributes(mergedAnnotation);
5049
propertySources.addFirst(new MapPropertySource("dynamicProperty", annotationProperties));
5150
PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources);
5251
String value = propertyResolver.resolvePlaceholders(dynamicProperty.value());
@@ -55,4 +54,13 @@ DynamicPropertyRegistryProperty createRegistryProperty(MergedAnnotation<DynamicP
5554
return new DynamicPropertyRegistryProperty(name, () -> expression.getValue(rootObject.get()));
5655
}
5756

57+
private Map<String, Object> collectAttributes(MergedAnnotation<?> mergedAnnotation) {
58+
Map<String, Object> attributes = new HashMap<>();
59+
for (MergedAnnotation<?> metaSource = mergedAnnotation; metaSource != null; metaSource = metaSource
60+
.getMetaSource()) {
61+
attributes.putAll(metaSource.asMap());
62+
}
63+
return attributes;
64+
}
65+
5866
}

spring-boot-testjars/src/main/java/org/springframework/experimental/boot/test/context/OAuth2ClientProviderIssuerUri.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,23 @@
3434
@Target(ElementType.METHOD)
3535
@Retention(RetentionPolicy.RUNTIME)
3636
@Documented
37-
@DynamicProperty(name = "spring.security.oauth2.client.provider.${providerName}.issuer-uri", value = "")
37+
@DynamicPortUrl(name = "spring.security.oauth2.client.provider.${providerName}.issuer-uri")
3838
public @interface OAuth2ClientProviderIssuerUri {
3939

4040
/**
4141
* Allows overriding the value of the property. The default is "'http://127.0.0.1:' +
4242
* port".
4343
* @return the value of the property.
4444
*/
45-
@AliasFor(annotation = DynamicProperty.class)
46-
String value() default "'http://127.0.0.1:' + port";
45+
@AliasFor(annotation = DynamicPortUrl.class)
46+
String host() default "127.0.0.1";
47+
48+
/**
49+
* Allows specifying a context root. The default is to have no context root.
50+
* @return the name of the provider used in the property name.
51+
*/
52+
@AliasFor(annotation = DynamicPortUrl.class)
53+
String contextRoot() default "";
4754

4855
/**
4956
* Allows overriding the providerName portion of the property name. Default is

spring-boot-testjars/src/test/java/org/springframework/experimental/boot/test/context/DynamicPropertyRegistryPropertyFactoryTests.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ void issuerUriWhenValueWithVariable() throws Exception {
8080
assertThat(registryProperty.value().get()).isEqualTo("Hello Rob");
8181
}
8282

83+
@Test
84+
void dynamicPortUrlWhenDefault() throws Exception {
85+
MergedAnnotation<DynamicProperty> dynamicProperty = dynamicPropertyFrom("dynamicPortUrlWithDefault");
86+
DynamicPropertyRegistryProperty registryProperty = this.propertyFactory.createRegistryProperty(dynamicProperty,
87+
() -> new WebServer());
88+
assertThat(registryProperty.name()).isEqualTo("message.url");
89+
assertThat(registryProperty.value().get()).isEqualTo("http://localhost:1234");
90+
}
91+
92+
@Test
93+
void dynamicPortUrlWhenOverride() throws Exception {
94+
MergedAnnotation<DynamicProperty> dynamicProperty = dynamicPropertyFrom("dynamicPortUrlWithOverride");
95+
DynamicPropertyRegistryProperty registryProperty = this.propertyFactory.createRegistryProperty(dynamicProperty,
96+
() -> new WebServer());
97+
assertThat(registryProperty.name()).isEqualTo("message.url");
98+
assertThat(registryProperty.value().get()).isEqualTo("http://127.0.0.1:1234/messages");
99+
}
100+
83101
private MergedAnnotation<DynamicProperty> dynamicPropertyFrom(String methodName) throws NoSuchMethodException {
84102
MergedAnnotations mergedAnnotations = MergedAnnotations.from(getClass().getDeclaredMethod(methodName));
85103
return mergedAnnotations.get(DynamicProperty.class);
@@ -95,7 +113,7 @@ static void issueUri() {
95113

96114
}
97115

98-
@OAuth2ClientProviderIssuerUri("'http://localhost:' + port")
116+
@OAuth2ClientProviderIssuerUri(host = "localhost")
99117
static void issueUriWithOverriddenValue() {
100118

101119
}
@@ -110,6 +128,14 @@ static void valueWithVariable() {
110128

111129
}
112130

131+
@DynamicPortUrl(name = "message.url")
132+
static void dynamicPortUrlWithDefault() {
133+
}
134+
135+
@DynamicPortUrl(name = "message.url", host = "127.0.0.1", contextRoot = "/messages")
136+
static void dynamicPortUrlWithOverride() {
137+
}
138+
113139
@Retention(RetentionPolicy.RUNTIME)
114140
@DynamicProperty(name = "message", value = "'Hello ${firstName}'")
115141
@interface ValueWithVariable {

0 commit comments

Comments
 (0)