Skip to content

Commit be995b5

Browse files
authored
Merge pull request #15194 from jdaugherty/7.0.x
fix - #15193 fix empty spring profile causing configuration to not load
2 parents 19cbf83 + e11277f commit be995b5

File tree

2 files changed

+204
-52
lines changed

2 files changed

+204
-52
lines changed

grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919
package org.grails.config
2020

21-
import java.util.regex.Pattern
22-
2321
import groovy.transform.CompileDynamic
2422
import groovy.transform.CompileStatic
2523
import groovy.transform.EqualsAndHashCode
@@ -38,15 +36,6 @@ class NavigableMap implements Map<String, Object>, Cloneable {
3836

3937
private static final Logger LOG = LoggerFactory.getLogger(NavigableMap)
4038

41-
private static final Pattern SPLIT_PATTERN = ~/\./
42-
private static final String SPRING_PROFILES = 'spring.profiles.active'
43-
private static final String SPRING = 'spring'
44-
private static final String CONFIG = 'config'
45-
private static final String ACTIVATE = 'activate'
46-
private static final String ON_PROFILE = 'on-profile'
47-
private static final String PROFILES = 'profiles'
48-
private static final String SUBSCRIPT_REGEX = /((.*)\[(\d+)\]).*/
49-
5039
final NavigableMap rootConfig
5140
final List<String> path
5241
final Map<String, Object> delegateMap
@@ -159,7 +148,7 @@ class NavigableMap implements Map<String, Object>, Cloneable {
159148
Map sourceMap,
160149
boolean parseFlatKeys) {
161150

162-
if (springProfileExclude(sourceMap, path)) {
151+
if (isSourceMapExcludedBySpringProfile(sourceMap, path)) {
163152
return
164153
}
165154

@@ -184,51 +173,41 @@ class NavigableMap implements Map<String, Object>, Cloneable {
184173
}
185174
}
186175

187-
private static boolean springProfileExclude(Map sourceMap, String path) {
176+
private static Object resolveConfigMapValue(Map map, Object... keys) {
177+
keys.inject(map) { acc, key -> acc instanceof Map ? acc[key] : null }
178+
}
188179

189-
// Is there an active Spring profile?
190-
def activeSpringProfile = System.getProperty(SPRING_PROFILES)
180+
private static boolean isSourceMapExcludedBySpringProfile(Map configSource, String path) {
191181

192-
// Is there a 'spring.config.activate.on-profile' property defined in the source map?
193-
def sourceMapProfile1 = ((Map) ((Map)((Map)sourceMap?.get(SPRING))?.get(CONFIG))?.get(ACTIVATE))?.get(ON_PROFILE)
194-
if (!sourceMapProfile1 && path == "$SPRING.$CONFIG.$ACTIVATE") {
195-
sourceMapProfile1 = sourceMap?.get(ON_PROFILE)
196-
}
197-
if (!sourceMapProfile1) {
198-
sourceMapProfile1 = sourceMap.get("$SPRING.$CONFIG.$ACTIVATE.$ON_PROFILE" as String)
199-
}
200-
if (sourceMapProfile1 && !activeSpringProfile) {
201-
// There is a spring.config.activate.on-profile property defined in this sourceMap, but there is no active spring profile
202-
return true
203-
}
204-
if (sourceMapProfile1 == activeSpringProfile) {
205-
// The active spring profile matches the spring.config.activate.on-profile property in this sourceMap
206-
return false
207-
}
182+
// get the active spring profile: treat empty string as null
183+
def active = System.getProperty('spring.profiles.active')?.trim() ?: null
208184

209-
// Is there a 'spring.profiles' property defined in the source map? (Old way of Spring profiles activation)
210-
def sourceMapProfile2 = ((Map) sourceMap?.get(SPRING))?.get(PROFILES)
211-
if (!sourceMapProfile2 && path == SPRING) {
212-
sourceMapProfile2 = sourceMap?.get(PROFILES)
213-
}
214-
if (!sourceMapProfile2) {
215-
sourceMapProfile2 = sourceMap.get("$SPRING.$PROFILES" as String)
216-
}
217-
if (sourceMapProfile1 && !activeSpringProfile) {
218-
// There is a spring.config.activate.on-profile property defined in this sourceMap, but there is no active spring profile
219-
return true
220-
}
221-
if (sourceMapProfile2 == activeSpringProfile) {
222-
// The active spring profile matches the spring.profiles property in this sourceMap
223-
return false
224-
}
185+
// lookup 'spring.config.activate.on-profile' in this config source
186+
def onProfile =
187+
resolveConfigMapValue(configSource, 'spring', 'config', 'activate', 'on-profile') ?:
188+
(path == 'spring.config.activate' ? configSource['on-profile'] : null) ?:
189+
configSource['spring.config.activate.on-profile']
225190

226-
if (activeSpringProfile && !sourceMapProfile1 && !sourceMapProfile2) {
227-
// There is no spring profile defined in this sourceMap, it should always be included
228-
return false
229-
}
191+
// no active profile is set but 'spring.config.activate.on-profile' is set in this config source -> exclude it
192+
if (!active && onProfile) return true
193+
// active profile is set and matches 'spring.config.activate.on-profile' in this config source -> include it
194+
if (active && onProfile == active) return false
195+
196+
// lookup (legacy) 'spring.profiles' in this config source
197+
def profiles =
198+
resolveConfigMapValue(configSource, 'spring', 'profiles') ?:
199+
(path == 'spring' ? configSource['profiles'] : null) ?:
200+
configSource['spring.profiles']
201+
202+
// no active profile is set but 'spring.profiles' is set in this config source -> exclude it
203+
if (!active && profiles) return true
204+
// active profile is set and matches 'spring.profiles' in this config source -> include it
205+
if (active && profiles == active) return false
206+
207+
// active profile is not required for this this source map -> include it
208+
if (!onProfile && !profiles) return false
230209

231-
// We can skip this sourceMap as it defines a spring profile that is not active
210+
// a profile constraint exists but doesn't match the active profile -> exclude
232211
return true
233212
}
234213

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package grails.config
20+
21+
import spock.lang.Specification
22+
import spock.lang.Unroll
23+
import spock.util.environment.RestoreSystemProperties
24+
25+
import org.grails.config.NavigableMap
26+
27+
@RestoreSystemProperties
28+
class isExcludedBySpringProfileSpec extends Specification {
29+
30+
def 'on-profile present in config source but no active profile is set => exclude'() {
31+
given:
32+
def src = nestedOnProfile('dev')
33+
def path = 'whatever'
34+
35+
expect:
36+
NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
37+
}
38+
39+
def 'on-profile matches active profile => include'() {
40+
given:
41+
System.setProperty('spring.profiles.active', 'dev')
42+
43+
and:
44+
def src = nestedOnProfile('dev')
45+
def path = 'does.not.matter'
46+
47+
expect:
48+
!NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
49+
}
50+
51+
def 'profiles matches active => include'() {
52+
given:
53+
System.setProperty('spring.profiles.active', 'prod')
54+
55+
and:
56+
def src = nestedProfiles('prod')
57+
def path = 'does.not.matter'
58+
59+
expect:
60+
!NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
61+
}
62+
63+
def 'profiles present in config but no active profile set => exclude'() {
64+
given:
65+
def src = nestedProfiles('prod')
66+
def path = 'does.not.matter'
67+
68+
expect:
69+
NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
70+
}
71+
72+
def 'empty string active profile is treated as null'() {
73+
given:
74+
System.setProperty('spring.profiles.active', '') // should behave like no active profile
75+
76+
and:
77+
def src = nestedOnProfile('dev')
78+
def path = 'does.not.matter'
79+
80+
expect:
81+
NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
82+
}
83+
84+
@Unroll
85+
def 'on-profile lookup via #desc is honored (active=#active -> exclude=#excludeExpected)'() {
86+
given:
87+
if (active) System.setProperty('spring.profiles.active', active)
88+
else System.clearProperty('spring.profiles.active')
89+
90+
and:
91+
def src = srcBuilder('qa')
92+
93+
expect:
94+
NavigableMap.isSourceMapExcludedBySpringProfile(src, path) == excludeExpected
95+
96+
where:
97+
desc | srcBuilder | path | active | excludeExpected
98+
'nested map' | this.&nestedOnProfile | 'any' | null | true // on-profile present, no active => exclude
99+
'nested map (match)' | this.&nestedOnProfile | 'any' | 'qa' | false // matches => include
100+
'relative via path match' | this.&relativeOnProfile | 'spring.config.activate' | 'qa' | false
101+
'relative via path no act' | this.&relativeOnProfile | 'spring.config.activate' | null | true
102+
'dotted key' | this.&dottedOnProfile | 'any' | 'qa' | false
103+
'dotted key no active' | this.&dottedOnProfile | 'any' | null | true
104+
'non-match' | this.&nestedOnProfile | 'any' | 'dev' | true // profile set but different from active => exclude
105+
}
106+
107+
@Unroll
108+
def 'legacy spring.profiles lookup via #desc is honored (active=#active -> exclude=#excludeExpected)'() {
109+
given:
110+
if (active) System.setProperty('spring.profiles.active', active)
111+
else System.clearProperty('spring.profiles.active')
112+
113+
and:
114+
def src = srcBuilder('prod')
115+
116+
expect:
117+
NavigableMap.isSourceMapExcludedBySpringProfile(src, path) == excludeExpected
118+
119+
where:
120+
desc | srcBuilder | path | active | excludeExpected
121+
'nested map' | this.&nestedProfiles | 'any' | 'prod' | false // matches => include
122+
'nested map no active' | this.&nestedProfiles | 'any' | null | true // present but no active => exclude
123+
'relative via path match' | this.&relativeProfiles | 'spring' | 'prod' | false
124+
'relative via path no act' | this.&relativeProfiles | 'spring' | null | true
125+
'dotted key (match)' | this.&dottedProfiles | 'any' | 'prod' | false
126+
'non-match' | this.&nestedProfiles | 'any' | 'qa' | true // different => exclude
127+
}
128+
129+
def 'active profile present but no profile keys in config => include'() {
130+
given:
131+
System.setProperty('spring.profiles.active', 'anything')
132+
133+
and:
134+
def src = [:]
135+
def path = 'any'
136+
137+
expect:
138+
!NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
139+
}
140+
141+
def 'no active and no profile keys => include'() {
142+
given:
143+
def src = [:]
144+
def path = 'any'
145+
146+
expect:
147+
!NavigableMap.isSourceMapExcludedBySpringProfile(src, path)
148+
}
149+
150+
private static Map nestedOnProfile(String value) {
151+
['spring': ['config': ['activate': ['on-profile': value]]]]
152+
}
153+
154+
private static Map relativeOnProfile(String value) {
155+
['on-profile': value]
156+
}
157+
158+
private static Map dottedOnProfile(String value) {
159+
['spring.config.activate.on-profile': value]
160+
}
161+
162+
private static Map nestedProfiles(String value) {
163+
['spring': ['profiles': value]]
164+
}
165+
166+
private static Map relativeProfiles(String value) {
167+
['profiles': value]
168+
}
169+
170+
private static Map dottedProfiles(String value) {
171+
['spring.profiles': value]
172+
}
173+
}

0 commit comments

Comments
 (0)