Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-commons.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,21 @@ public class MyConfiguration {

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

== CachedRandomPropertySource

Spring Cloud Context provides a `PropertySource` that caches random values based on a key. Outside of the caching
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`].
This random value might be useful in the case where you want a random value that is consistent even after the Spring Application
context restarts. The property value takes the form of `cachedrandom.[yourkey].[type]` where `yourkey` is the key in the cache. The `type` value can
be any type supported by Spring Boot's `RandomValuePropertySource`.

====
[source,properties,indent=0]
----
myrandom=${cachedrandom.appname.value}
----
====

== Configuration Properties

To see the list of all Spring Cloud Commons related configuration properties please check link:appendix.html[the Appendix page].
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright 2012-2020 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.
* 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.springframework.cloud.util.random;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;

/**
* @author Ryan Baxter
*/
public class CachedRandomPropertySource
extends PropertySource<RandomValuePropertySource> {

private static final String NAME = "cachedrandom";

private static final String PREFIX = NAME + ".";

private static Map<String, Map<String, Object>> cache = new ConcurrentHashMap<>();

public CachedRandomPropertySource(
RandomValuePropertySource randomValuePropertySource) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever use, I was thinking of copying.

super(NAME, randomValuePropertySource);

}

CachedRandomPropertySource(RandomValuePropertySource randomValuePropertySource,
Map<String, Map<String, Object>> cache) {
super(NAME, randomValuePropertySource);
this.cache = cache;
}

@Override
public Object getProperty(String name) {
if (!name.startsWith(PREFIX) || name.length() == PREFIX.length()) {
return null;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Generating random property for '" + name + "'");
}
// TO avoid any weirdness from the type or key including a "." we look for the
// last "." and substring everything instead of splitting on the "."
String keyAndType = name.substring(PREFIX.length());
int lastIndexOfDot = keyAndType.lastIndexOf(".");
if (lastIndexOfDot < 0) {
return null;
}
String key = keyAndType.substring(0, lastIndexOfDot);
String type = keyAndType.substring(lastIndexOfDot + 1);
if (StringUtils.hasText(key) && StringUtils.hasText(type)) {
return getRandom(type, key);
}
else {
return null;
}
}
}

private Object getRandom(String type, String key) {
Map<String, Object> randomValueCache = getCacheForKey(key);
if (logger.isDebugEnabled()) {
logger.debug("Looking in random cache for key " + key + " with type " + type);
}
return randomValueCache.computeIfAbsent(type, (theType) -> {
if (logger.isDebugEnabled()) {
logger.debug(
"No random value found in cache for key and value, generating a new value");
}
return getSource().getProperty("random." + type);
});
}

private Map<String, Object> getCacheForKey(String key) {
if (logger.isDebugEnabled()) {
logger.debug("Looking in random cache for key: " + key);
}
return cache.computeIfAbsent(key, theKey -> {
if (logger.isDebugEnabled()) {
logger.debug("No cached value found for key: " + key);
}
return new ConcurrentHashMap<>();
});
}

public static void clearCache() {
if (cache != null) {
cache.clear();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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.
* 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.springframework.cloud.util.random;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

/**
* @author Ryan Baxter
*/
@Configuration(proxyBeanMethods = false)
public class CachedRandomPropertySourceAutoConfiguration {

@Autowired
ConfigurableEnvironment environment;

@PostConstruct
public void initialize() {
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource propertySource = propertySources
.get(RandomValuePropertySource.RANDOM_PROPERTY_SOURCE_NAME);
if (propertySource != null) {
propertySources.addLast(new CachedRandomPropertySource(
RandomValuePropertySource.class.cast(propertySource)));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

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

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

@Test
@DirtiesContext
public void testCachedRandom() {
long cachedRandomLong = properties.getCachedRandomLong();
long randomLong = properties.randomLong();
then(cachedRandomLong).isNotNull();
this.refresher.refresh();
then(randomLong).isNotEqualTo(properties.randomLong());
then(cachedRandomLong).isEqualTo(properties.cachedRandomLong);
}

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TestProperties.class)
@EnableAutoConfiguration
Expand All @@ -96,6 +107,10 @@ protected static class TestProperties {

private int delay;

private Long cachedRandomLong;

private Long randomLong;

@ManagedAttribute
public String getMessage() {
return this.message;
Expand All @@ -114,6 +129,22 @@ public void setDelay(int delay) {
this.delay = delay;
}

public long getCachedRandomLong() {
return cachedRandomLong;
}

public void setCachedRandomLong(long cachedRandomLong) {
this.cachedRandomLong = cachedRandomLong;
}

public long randomLong() {
return randomLong;
}

public void setRandomLong(long randomLong) {
this.randomLong = randomLong;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2012-2020 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.
* 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.springframework.cloud.util.random;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.test.annotation.DirtiesContext;

import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
@DirtiesContext
public class CachedRandomPropertySourceTests {

@Mock
RandomValuePropertySource randomValuePropertySource;

@Before
public void setup() {

when(randomValuePropertySource.getProperty(eq("random.long")))
.thenReturn(new Long(1234));
}

@Test
public void getProperty() {
Map<String, Map<String, Object>> cache = new HashMap<>();
Map<String, Object> typeCache = new HashMap<>();
typeCache.put("long", new Long(5678));
Map<String, Object> spyedTypeCache = spy(typeCache);
cache.put("foo", spyedTypeCache);
Map<String, Map<String, Object>> spyedCache = spy(cache);

CachedRandomPropertySource cachedRandomPropertySource = new CachedRandomPropertySource(
randomValuePropertySource, spyedCache);
then(cachedRandomPropertySource.getProperty("foo.app.long")).isNull();
then(cachedRandomPropertySource.getProperty("cachedrandom.app")).isNull();

then(cachedRandomPropertySource.getProperty("cachedrandom.app.long"))
.isEqualTo(new Long(1234));
then(cachedRandomPropertySource.getProperty("cachedrandom.foo.long"))
.isEqualTo(new Long(5678));
verify(spyedCache, times(1)).computeIfAbsent(eq("app"), isA(Function.class));
verify(spyedTypeCache, times(1)).computeIfAbsent(eq("long"), isA(Function.class));
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.TestBootstrapConfiguration,\
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration
org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration


org.springframework.boot.env.EnvironmentPostProcessor=\
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
message:Hello scope!
delay:0
cachedRandomLong: ${cachedrandom.app.long}
randomLong: ${random.long}
debug:true
#logging.level.org.springframework.web: DEBUG
#logging.level.org.springframework.context.annotation: DEBUG
Expand Down