Skip to content

Commit 4ff0c1f

Browse files
authored
Support Environments Concept in Micronaut Context (#12809)
* Support Environments Concept in Micronaut Context Implemented a new MicronautYamlPropertySourceLoader which reads environment specific properties from application.yml file based on the current configured environment. Fixes #12082 * Update GroovyConfigPropertySourceLoaderSpec.groovy
1 parent bb87a9b commit 4ff0c1f

12 files changed

+366
-8
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2017-2020 original 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+
package org.grails.core.cfg
17+
18+
import groovy.transform.Internal
19+
import io.micronaut.context.env.yaml.ConstructIsoTimestampString
20+
import org.yaml.snakeyaml.constructor.SafeConstructor
21+
import org.yaml.snakeyaml.nodes.MappingNode
22+
import org.yaml.snakeyaml.nodes.SequenceNode
23+
import org.yaml.snakeyaml.nodes.Tag
24+
25+
/**
26+
* Yaml constructor to create containers with sensible
27+
* default array bounds.
28+
*
29+
* @author James Kleeh
30+
* @since 6.0.0
31+
*/
32+
@Internal
33+
class CustomSafeConstructor extends SafeConstructor {
34+
CustomSafeConstructor() {
35+
yamlConstructors.put(Tag.TIMESTAMP, new ConstructIsoTimestampString())
36+
}
37+
38+
@Override
39+
protected Map<Object, Object> newMap(MappingNode node) {
40+
return createDefaultMap(node.getValue().size())
41+
}
42+
43+
@Override
44+
protected List<Object> newList(SequenceNode node) {
45+
return createDefaultList(node.getValue().size())
46+
}
47+
}
48+

grails-core/src/main/groovy/org/grails/core/cfg/MicronautGroovyPropertySourceLoader.groovy

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.grails.core.cfg
22

3+
import grails.plugins.GrailsPlugin
34
import grails.util.BuildSettings
45
import grails.util.Environment
56
import grails.util.Metadata
67
import groovy.transform.CompileStatic
78
import io.micronaut.context.env.AbstractPropertySourceLoader
9+
import io.micronaut.context.env.MapPropertySource
810
import io.micronaut.context.exceptions.ConfigurationException
911
import io.micronaut.core.io.ResourceLoader
1012
import org.grails.config.NavigableMap
@@ -22,7 +24,7 @@ class MicronautGroovyPropertySourceLoader extends AbstractPropertySourceLoader {
2224

2325
@Override
2426
int getOrder() {
25-
return DEFAULT_POSITION
27+
return DEFAULT_POSITION + 1
2628
}
2729

2830
@Override
@@ -37,9 +39,10 @@ class MicronautGroovyPropertySourceLoader extends AbstractPropertySourceLoader {
3739
appVersion: Metadata.getCurrent().getApplicationVersion())
3840
try {
3941
def configObject = configSlurper.parse(input.getText("UTF-8"))
40-
def propertySource = new NavigableMap()
42+
final Map<String, Object> propertySource = new NavigableMap()
4143
propertySource.merge(configObject.flatten(), false)
4244
finalMap.putAll(propertySource)
45+
processEnvironmentSpecificProperties(finalMap, propertySource)
4346
} catch (Throwable e) {
4447
throw new ConfigurationException("Exception occurred reading configuration [" + name + "]: " + e.getMessage(), e)
4548
}
@@ -68,4 +71,19 @@ class MicronautGroovyPropertySourceLoader extends AbstractPropertySourceLoader {
6871
return Collections.singleton("groovy")
6972
}
7073

74+
@Override
75+
protected MapPropertySource createPropertySource(String name, Map<String, Object> map, int order) {
76+
return super.createPropertySource("grails.$name", map, order)
77+
}
78+
79+
void processEnvironmentSpecificProperties(finalMap, Map<String, Object> propertySource) {
80+
final String environmentName = Environment.current.name
81+
if (environmentName != null) {
82+
final String environmentPrefix = GrailsPlugin.ENVIRONMENTS + '.' + environmentName + '.'
83+
propertySource.keySet().stream()
84+
.filter(k -> k.startsWith(environmentPrefix))
85+
.forEach(propertyName -> { finalMap[propertyName.substring(environmentPrefix.length())] = propertySource.get(propertyName) })
86+
}
87+
}
88+
7189
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.grails.core.cfg
2+
3+
import grails.plugins.GrailsPlugin
4+
import grails.util.Environment
5+
import groovy.transform.CompileStatic
6+
import io.micronaut.context.env.MapPropertySource
7+
import io.micronaut.context.env.yaml.YamlPropertySourceLoader
8+
9+
/**
10+
* Load properties from a YAML file. This class extends the Micronaut default implementation {@link io.micronaut.context.env.yaml.YamlPropertySourceLoader} where it adds support for Micronaut beans receive configuration from environments block.
11+
*
12+
* @author Puneet Behl
13+
* @since 6.0.0
14+
*/
15+
@CompileStatic
16+
class MicronautYamlPropertySourceLoader extends YamlPropertySourceLoader {
17+
18+
@Override
19+
int getOrder() {
20+
return super.getOrder() + 1
21+
}
22+
23+
@Override
24+
protected MapPropertySource createPropertySource(String name, Map<String, Object> map, int order) {
25+
return super.createPropertySource("grails.$name", map, order)
26+
}
27+
28+
/**
29+
* Only process environment specific entries as the other entries are already processed by Micronaut default {@link io.micronaut.context.env.yaml.YamlPropertySourceLoader}
30+
*
31+
* @param finalMap The map with all the properties processed
32+
* @param map The map to process
33+
* @param prefix The prefix for the keys
34+
*/
35+
@Override
36+
protected void processMap(Map<String, Object> finalMap, Map map, String prefix) {
37+
final String env = Environment.current.name
38+
if (env != null) {
39+
final String grailsEnvironmentPrefix = GrailsPlugin.ENVIRONMENTS + '.' + env + '.'
40+
((Map<String, Object>) map).entrySet().stream()
41+
.filter(e -> e.key instanceof String && (((String) e.key).startsWith(GrailsPlugin.ENVIRONMENTS) || prefix.startsWith(GrailsPlugin.ENVIRONMENTS)))
42+
.forEach(e -> {
43+
Map.Entry entry = (Map.Entry) e
44+
String key = entry.getKey().toString()
45+
Object value = entry.getValue()
46+
if (value instanceof Map && !((Map) value).isEmpty()) {
47+
processMap(finalMap, (Map) value, prefix + key + '.')
48+
} else if ((prefix + key).startsWith(grailsEnvironmentPrefix)) {
49+
finalMap.put((prefix + key).substring(grailsEnvironmentPrefix.length()), value)
50+
}
51+
})
52+
}
53+
}
54+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
org.grails.core.cfg.MicronautYamlPropertySourceLoader
12
org.grails.core.cfg.MicronautGroovyPropertySourceLoader
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.grails.core.cfg
2+
3+
import grails.util.Environment
4+
5+
trait EnvironmentAwareSpec {
6+
7+
Environment previous = null
8+
9+
void setEnvironment(Environment environment) {
10+
if (System.hasProperty(Environment.KEY)) {
11+
previous = Environment.getEnvironment(System.getProperty(Environment.KEY))
12+
}
13+
System.setProperty(Environment.KEY, environment ? environment.getName() : '')
14+
}
15+
16+
void resetEnvironment() {
17+
if (previous) {
18+
System.setProperty(Environment.KEY, previous.getName())
19+
} else {
20+
System.setProperty(Environment.KEY, '')
21+
}
22+
}
23+
}

grails-core/src/test/groovy/org/grails/core/cfg/GroovyConfigPropertySourceLoaderSpec.groovy

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.grails.core.cfg
22

3+
import grails.util.Environment
34
import org.grails.config.PropertySourcesConfig
45
import org.springframework.core.env.MutablePropertySources
56
import org.springframework.core.env.PropertySources
@@ -8,7 +9,11 @@ import org.springframework.core.io.Resource
89
import spock.lang.Specification
910

1011
@SuppressWarnings("GrMethodMayBeStatic")
11-
class GroovyConfigPropertySourceLoaderSpec extends Specification {
12+
class GroovyConfigPropertySourceLoaderSpec extends Specification implements EnvironmentAwareSpec {
13+
14+
void setup() {
15+
resetEnvironment()
16+
}
1217

1318
void "test loading multiple configuration files"() {
1419
setup:
@@ -17,6 +22,7 @@ class GroovyConfigPropertySourceLoaderSpec extends Specification {
1722

1823
GroovyConfigPropertySourceLoader groovyPropertySourceLoader = new GroovyConfigPropertySourceLoader()
1924
Map<String, Object> finalMap = [:]
25+
environment = Environment.TEST
2026

2127
when:
2228
PropertySources propertySources = new MutablePropertySources()
@@ -25,8 +31,9 @@ class GroovyConfigPropertySourceLoaderSpec extends Specification {
2531
def config = new PropertySourcesConfig(propertySources)
2632

2733
then:
28-
config.size() == 7
34+
config.size() == 9
2935
config.getProperty("my.local.var", String.class) == "test"
36+
config.getProperty("foo.bar", String.class) == "test"
3037
config.getProperty("userHomeVar", String.class)
3138

3239
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.grails.core.cfg
2+
3+
import grails.util.Environment
4+
import grails.util.SupplierUtil
5+
import io.micronaut.context.ApplicationContext
6+
import io.micronaut.context.ApplicationContextBuilder
7+
import io.micronaut.context.ApplicationContextConfigurer
8+
import io.micronaut.context.annotation.ContextConfigurer
9+
import io.micronaut.context.env.PropertySource
10+
import io.micronaut.spring.context.factory.MicronautBeanFactoryConfiguration
11+
import org.springframework.core.convert.ConversionService
12+
import org.springframework.core.env.ConfigurableEnvironment
13+
import org.springframework.core.env.PropertyResolver
14+
import org.springframework.lang.NonNull
15+
16+
import java.util.function.Supplier
17+
18+
trait MicronautContextAwareSpec {
19+
20+
ApplicationContext micronautContext
21+
String[] environments = new String[5]
22+
Supplier<ClassLoader> classLoader = SupplierUtil.memoized(() -> this.getClass().getClassLoader())
23+
24+
ApplicationContextBuilder micronautContextBuilder() {
25+
if (Environment.current != null) {
26+
environments[0] = Environment.current.getName()
27+
}
28+
final ApplicationContextBuilder builder = ApplicationContext.builder()
29+
.classLoader(classLoader.get())
30+
.deduceEnvironment(false)
31+
.propertySources(PropertySource.of("grails-config", [(MicronautBeanFactoryConfiguration.PREFIX + ".bean-excludes"): (Object) beanExcludes]))
32+
.environments(environments)
33+
34+
builder
35+
}
36+
37+
ApplicationContext getMicronautContext() {
38+
if (micronautContext == null) {
39+
micronautContext = micronautContextBuilder().build()
40+
}
41+
micronautContext
42+
}
43+
44+
ApplicationContext start() {
45+
if (micronautContext != null) {
46+
micronautContext.start()
47+
}
48+
}
49+
50+
List<Class<?>> getBeanExcludes() {
51+
List beanExcludes = []
52+
beanExcludes.add(ConversionService.class)
53+
beanExcludes.add(org.springframework.core.env.Environment.class)
54+
beanExcludes.add(PropertyResolver.class)
55+
beanExcludes.add(ConfigurableEnvironment.class)
56+
def objectMapper = io.micronaut.core.reflect.ClassUtils.forName("com.fasterxml.jackson.databind.ObjectMapper", classLoader.get()).orElse(null)
57+
if (objectMapper != null) {
58+
beanExcludes.add(objectMapper)
59+
}
60+
beanExcludes
61+
}
62+
63+
void setClassLoader(ClassLoader classLoader) {
64+
this.classLoader = SupplierUtil.memoized(() -> classLoader)
65+
}
66+
67+
}

grails-core/src/test/groovy/org/grails/core/cfg/MicronautGroovyPropertySourceLoaderSpec.groovy

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
package org.grails.core.cfg
22

3+
import grails.util.Environment
34
import grails.util.Metadata
4-
import io.micronaut.context.exceptions.ConfigurationException
5-
import org.grails.config.NavigableMap
65
import spock.lang.Specification
76

87
@SuppressWarnings("GrMethodMayBeStatic")
9-
class MicronautGroovyPropertySourceLoaderSpec extends Specification {
8+
class MicronautGroovyPropertySourceLoaderSpec extends Specification implements EnvironmentAwareSpec{
9+
10+
void setup() {
11+
resetEnvironment()
12+
}
13+
14+
void "test read environment specific property" () {
15+
setup:
16+
final URL resource = getClass().getClassLoader().getResource("test-application.groovy")
17+
final MicronautGroovyPropertySourceLoader groovyPropertySourceLoader = new MicronautGroovyPropertySourceLoader()
18+
environment = Environment.TEST
19+
20+
when:
21+
final Map<String, Object> finalMap = groovyPropertySourceLoader.read("test-application.groovy", resource.openStream())
22+
23+
then:
24+
finalMap.get("foo.bar") == "test"
25+
}
26+
27+
void "test read environment specific property takes precedence"() {
28+
setup:
29+
final URL resource = getClass().getClassLoader().getResource("test-application.groovy")
30+
final MicronautGroovyPropertySourceLoader groovyPropertySourceLoader = new MicronautGroovyPropertySourceLoader()
31+
32+
when:
33+
final Map<String, Object> finalMap = groovyPropertySourceLoader.read("test-application.groovy", resource.openStream())
34+
35+
then:
36+
finalMap.get("foo.bar") == "default"
37+
38+
when:
39+
environment = Environment.TEST
40+
41+
then:
42+
final Map<String, Object> finalMap2 = groovyPropertySourceLoader.read("test-application.groovy", resource.openStream())
43+
finalMap2.get("foo.bar") == "test"
44+
}
1045

1146
void "test parsing configuration file with DSL"() {
1247

0 commit comments

Comments
 (0)