Skip to content

Commit bea81f7

Browse files
author
Ryan Baxter
committed
Cherry picking commit 4119a9c
1 parent 0e3ea33 commit bea81f7

File tree

9 files changed

+290
-3
lines changed

9 files changed

+290
-3
lines changed

docs/src/main/asciidoc/spring-cloud-commons.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,21 @@ public class MyConfiguration {
10561056

10571057
include::spring-cloud-circuitbreaker.adoc[leveloffset=+1]
10581058

1059+
== CachedRandomPropertySource
1060+
1061+
Spring Cloud Context provides a `PropertySource` that caches random values based on a key. Outside of the caching
1062+
functionality it works the same as Spring Boot's https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java[`RandomValuePropertySource`].
1063+
This random value might be useful in the case where you want a random value that is consistent even after the Spring Application
1064+
context restarts. The property value takes the form of `cachedrandom.[yourkey].[type]` where `yourkey` is the key in the cache. The `type` value can
1065+
be any type supported by Spring Boot's `RandomValuePropertySource`.
1066+
1067+
====
1068+
[source,properties,indent=0]
1069+
----
1070+
myrandom=${cachedrandom.appname.value}
1071+
----
1072+
====
1073+
10591074
== Configuration Properties
10601075

10611076
To see the list of all Spring Cloud Commons related configuration properties please check link:appendix.html[the Appendix page].
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2012-2020 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.cloud.util.random;
18+
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
22+
import org.springframework.boot.env.RandomValuePropertySource;
23+
import org.springframework.core.env.PropertySource;
24+
import org.springframework.util.StringUtils;
25+
26+
/**
27+
* @author Ryan Baxter
28+
*/
29+
public class CachedRandomPropertySource
30+
extends PropertySource<RandomValuePropertySource> {
31+
32+
private static final String NAME = "cachedrandom";
33+
34+
private static final String PREFIX = NAME + ".";
35+
36+
private static Map<String, Map<String, Object>> cache = new ConcurrentHashMap<>();
37+
38+
public CachedRandomPropertySource(
39+
RandomValuePropertySource randomValuePropertySource) {
40+
super(NAME, randomValuePropertySource);
41+
42+
}
43+
44+
CachedRandomPropertySource(RandomValuePropertySource randomValuePropertySource,
45+
Map<String, Map<String, Object>> cache) {
46+
super(NAME, randomValuePropertySource);
47+
this.cache = cache;
48+
}
49+
50+
@Override
51+
public Object getProperty(String name) {
52+
if (!name.startsWith(PREFIX) || name.length() == PREFIX.length()) {
53+
return null;
54+
}
55+
else {
56+
if (logger.isTraceEnabled()) {
57+
logger.trace("Generating random property for '" + name + "'");
58+
}
59+
// TO avoid any weirdness from the type or key including a "." we look for the
60+
// last "." and substring everything instead of splitting on the "."
61+
String keyAndType = name.substring(PREFIX.length());
62+
int lastIndexOfDot = keyAndType.lastIndexOf(".");
63+
if (lastIndexOfDot < 0) {
64+
return null;
65+
}
66+
String key = keyAndType.substring(0, lastIndexOfDot);
67+
String type = keyAndType.substring(lastIndexOfDot + 1);
68+
if (StringUtils.hasText(key) && StringUtils.hasText(type)) {
69+
return getRandom(type, key);
70+
}
71+
else {
72+
return null;
73+
}
74+
}
75+
}
76+
77+
private Object getRandom(String type, String key) {
78+
Map<String, Object> randomValueCache = getCacheForKey(key);
79+
if (logger.isDebugEnabled()) {
80+
logger.debug("Looking in random cache for key " + key + " with type " + type);
81+
}
82+
return randomValueCache.computeIfAbsent(type, (theType) -> {
83+
if (logger.isDebugEnabled()) {
84+
logger.debug(
85+
"No random value found in cache for key and value, generating a new value");
86+
}
87+
return getSource().getProperty("random." + type);
88+
});
89+
}
90+
91+
private Map<String, Object> getCacheForKey(String key) {
92+
if (logger.isDebugEnabled()) {
93+
logger.debug("Looking in random cache for key: " + key);
94+
}
95+
return cache.computeIfAbsent(key, theKey -> {
96+
if (logger.isDebugEnabled()) {
97+
logger.debug("No cached value found for key: " + key);
98+
}
99+
return new ConcurrentHashMap<>();
100+
});
101+
}
102+
103+
public static void clearCache() {
104+
if (cache != null) {
105+
cache.clear();
106+
}
107+
}
108+
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2012-2020 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.cloud.util.random;
18+
19+
import javax.annotation.PostConstruct;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.env.RandomValuePropertySource;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.core.env.ConfigurableEnvironment;
25+
import org.springframework.core.env.MutablePropertySources;
26+
import org.springframework.core.env.PropertySource;
27+
28+
/**
29+
* @author Ryan Baxter
30+
*/
31+
@Configuration(proxyBeanMethods = false)
32+
public class CachedRandomPropertySourceAutoConfiguration {
33+
34+
@Autowired
35+
ConfigurableEnvironment environment;
36+
37+
@PostConstruct
38+
public void initialize() {
39+
MutablePropertySources propertySources = environment.getPropertySources();
40+
PropertySource propertySource = propertySources
41+
.get(RandomValuePropertySource.RANDOM_PROPERTY_SOURCE_NAME);
42+
if (propertySource != null) {
43+
propertySources.addLast(new CachedRandomPropertySource(
44+
RandomValuePropertySource.class.cast(propertySource)));
45+
}
46+
}
47+
48+
}

spring-cloud-context/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
1515
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
1616
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
1717
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
18-
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
18+
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
19+
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration

spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
@RunWith(SpringRunner.class)
3939
@SpringBootTest(classes = TestConfiguration.class,
40-
properties = { "spring.datasource.hikari.read-only=false" })
40+
properties = { "spring.datasource.hikari.read-only=false", "debug=true" })
4141
public class ContextRefresherIntegrationTests {
4242

4343
@Autowired
@@ -81,6 +81,17 @@ public void testUpdateHikari() throws Exception {
8181
then(this.properties.getMessage()).isEqualTo("Hello scope!");
8282
}
8383

84+
@Test
85+
@DirtiesContext
86+
public void testCachedRandom() {
87+
long cachedRandomLong = properties.getCachedRandomLong();
88+
long randomLong = properties.randomLong();
89+
then(cachedRandomLong).isNotNull();
90+
this.refresher.refresh();
91+
then(randomLong).isNotEqualTo(properties.randomLong());
92+
then(cachedRandomLong).isEqualTo(properties.cachedRandomLong);
93+
}
94+
8495
@Configuration(proxyBeanMethods = false)
8596
@EnableConfigurationProperties(TestProperties.class)
8697
@EnableAutoConfiguration
@@ -96,6 +107,10 @@ protected static class TestProperties {
96107

97108
private int delay;
98109

110+
private Long cachedRandomLong;
111+
112+
private Long randomLong;
113+
99114
@ManagedAttribute
100115
public String getMessage() {
101116
return this.message;
@@ -114,6 +129,22 @@ public void setDelay(int delay) {
114129
this.delay = delay;
115130
}
116131

132+
public long getCachedRandomLong() {
133+
return cachedRandomLong;
134+
}
135+
136+
public void setCachedRandomLong(long cachedRandomLong) {
137+
this.cachedRandomLong = cachedRandomLong;
138+
}
139+
140+
public long randomLong() {
141+
return randomLong;
142+
}
143+
144+
public void setRandomLong(long randomLong) {
145+
this.randomLong = randomLong;
146+
}
147+
117148
}
118149

119150
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2012-2020 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.cloud.util.random;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.function.Function;
22+
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.mockito.Mock;
27+
import org.mockito.junit.MockitoJUnitRunner;
28+
29+
import org.springframework.boot.env.RandomValuePropertySource;
30+
import org.springframework.test.annotation.DirtiesContext;
31+
32+
import static org.assertj.core.api.BDDAssertions.then;
33+
import static org.mockito.ArgumentMatchers.eq;
34+
import static org.mockito.ArgumentMatchers.isA;
35+
import static org.mockito.Mockito.spy;
36+
import static org.mockito.Mockito.times;
37+
import static org.mockito.Mockito.verify;
38+
import static org.mockito.Mockito.when;
39+
40+
/**
41+
* @author Ryan Baxter
42+
*/
43+
@RunWith(MockitoJUnitRunner.class)
44+
@DirtiesContext
45+
public class CachedRandomPropertySourceTests {
46+
47+
@Mock
48+
RandomValuePropertySource randomValuePropertySource;
49+
50+
@Before
51+
public void setup() {
52+
53+
when(randomValuePropertySource.getProperty(eq("random.long")))
54+
.thenReturn(new Long(1234));
55+
}
56+
57+
@Test
58+
public void getProperty() {
59+
Map<String, Map<String, Object>> cache = new HashMap<>();
60+
Map<String, Object> typeCache = new HashMap<>();
61+
typeCache.put("long", new Long(5678));
62+
Map<String, Object> spyedTypeCache = spy(typeCache);
63+
cache.put("foo", spyedTypeCache);
64+
Map<String, Map<String, Object>> spyedCache = spy(cache);
65+
66+
CachedRandomPropertySource cachedRandomPropertySource = new CachedRandomPropertySource(
67+
randomValuePropertySource, spyedCache);
68+
then(cachedRandomPropertySource.getProperty("foo.app.long")).isNull();
69+
then(cachedRandomPropertySource.getProperty("cachedrandom.app")).isNull();
70+
71+
then(cachedRandomPropertySource.getProperty("cachedrandom.app.long"))
72+
.isEqualTo(new Long(1234));
73+
then(cachedRandomPropertySource.getProperty("cachedrandom.foo.long"))
74+
.isEqualTo(new Long(5678));
75+
verify(spyedCache, times(1)).computeIfAbsent(eq("app"), isA(Function.class));
76+
verify(spyedTypeCache, times(1)).computeIfAbsent(eq("long"), isA(Function.class));
77+
}
78+
79+
}

spring-cloud-context/src/test/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Bootstrap components
22
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
33
org.springframework.cloud.bootstrap.TestBootstrapConfiguration,\
4-
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration
4+
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration,\
5+
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
56

67

78
org.springframework.boot.env.EnvironmentPostProcessor=\

spring-cloud-context/src/test/resources/application.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
message:Hello scope!
22
delay:0
3+
cachedRandomLong: ${cachedrandom.app.long}
4+
randomLong: ${random.long}
35
debug:true
46
#logging.level.org.springframework.web: DEBUG
57
#logging.level.org.springframework.context.annotation: DEBUG

src/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
<suppress files=".*RefreshScopeConfigurationTests.*" checks="JavadocVariable"/>
1414
<suppress files=".*RefreshAutoConfigurationTests.*" checks="JavadocVariable"/>
1515
<suppress files=".*RefreshAutoConfigurationMoreClassPathTests.*" checks="JavadocVariable"/>
16+
<suppress files=".*EnvironmentDecryptApplicationInitializerTests.*" checks="JavadocVariable"/>
1617
</suppressions>

0 commit comments

Comments
 (0)