From b92fc877ab8dc8622e39252879b19a053c6af6bf Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Thu, 21 Nov 2024 13:21:07 -0500 Subject: [PATCH] fix #13655 - restore dot notation navigation --- .../org/grails/config/CodeGenConfig.groovy | 2 +- .../org/grails/config/NavigableMap.groovy | 197 ++++++++++++++++-- .../groovy/grails/config/ConfigMapSpec.groovy | 78 ++++++- .../grails/config/GrailsConfigSpec.groovy | 12 +- .../GrailsApplicationPostProcessor.groovy | 13 ++ .../config/NavigableMapPropertySource.groovy | 2 +- .../plugins/AbstractGrailsPluginManager.java | 13 ++ .../config/PropertySourceConfigSpec.groovy | 129 ++++++++++++ .../GrailsPlaceholderConfigurerSpec.groovy | 20 +- .../NavigableMapPropertySourceSpec.groovy | 16 +- .../config/PropertySourcesConfigSpec.groovy | 2 +- .../YamlPropertySourceLoaderSpec.groovy | 4 +- ...TransactionManagerPostProcessorSpec.groovy | 21 +- .../grails/web/codecs/HTMLCodecTests.groovy | 2 +- 14 files changed, 482 insertions(+), 29 deletions(-) diff --git a/grails-bootstrap/src/main/groovy/org/grails/config/CodeGenConfig.groovy b/grails-bootstrap/src/main/groovy/org/grails/config/CodeGenConfig.groovy index 5832fd4ce65..8083efdead3 100644 --- a/grails-bootstrap/src/main/groovy/org/grails/config/CodeGenConfig.groovy +++ b/grails-bootstrap/src/main/groovy/org/grails/config/CodeGenConfig.groovy @@ -178,7 +178,7 @@ class CodeGenConfig implements Cloneable, ConfigMap { } protected T convertToType(Object value, Class requiredType) { - if(value == null) { + if(value == null || value instanceof NavigableMap.NullSafeNavigator) { return null } else if(requiredType.isInstance(value)) { diff --git a/grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy b/grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy index fa7ad77002b..3b981951f01 100644 --- a/grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy +++ b/grails-bootstrap/src/main/groovy/org/grails/config/NavigableMap.groovy @@ -18,6 +18,7 @@ package org.grails.config import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode +import org.codehaus.groovy.runtime.DefaultGroovyMethods import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -47,14 +48,14 @@ class NavigableMap implements Map, Cloneable { final Map delegateMap final String dottedPath - public NavigableMap() { + NavigableMap() { rootConfig = this path = [] dottedPath = "" delegateMap = new LinkedHashMap<>() } - public NavigableMap(NavigableMap rootConfig, List path) { + NavigableMap(NavigableMap rootConfig, List path) { super() this.rootConfig = rootConfig this.path = path @@ -144,7 +145,7 @@ class NavigableMap implements Map, Cloneable { delegateMap.entrySet() } - public void merge(Map sourceMap, boolean parseFlatKeys=false) { + void merge(Map sourceMap, boolean parseFlatKeys=false) { mergeMaps(this, "", this, sourceMap, parseFlatKeys) } @@ -341,17 +342,17 @@ class NavigableMap implements Map, Cloneable { targetMap.put(sourceKey, newValue) } - public Object getAt(Object key) { + Object getAt(Object key) { getProperty(String.valueOf(key)) } - - public void setAt(Object key, Object value) { + + void setAt(Object key, Object value) { setProperty(String.valueOf(key), value) } - public Object getProperty(String name) { + Object getProperty(String name) { if (!containsKey(name)) { - return null + return new NullSafeNavigator(this, [name].asImmutable()) } Object result = get(name) if (!(result instanceof NavigableMap)) { @@ -361,12 +362,12 @@ class NavigableMap implements Map, Cloneable { } return result } - - public void setProperty(String name, Object value) { + + void setProperty(String name, Object value) { mergeMapEntry(rootConfig, dottedPath, this, name, value, false, true) } - - public Object navigate(String... path) { + + Object navigate(String... path) { return navigateMap(this, path) } @@ -392,8 +393,8 @@ class NavigableMap implements Map, Cloneable { } } } - - public NavigableMap navigateSubMap(List path, boolean createMissing) { + + NavigableMap navigateSubMap(List path, boolean createMissing) { NavigableMap rootMap = this NavigableMap currentMap = this StringBuilder accumulatedPath = new StringBuilder() @@ -488,4 +489,172 @@ class NavigableMap implements Map, Cloneable { boolean equals(Object obj) { return delegateMap.equals(obj) } + + /** + * @deprecated This class should be avoided due to known performance reasons. Use {@code config.getProperty(String key, Class targetType)} instead of dot based navigation. + */ + @Deprecated + @CompileStatic + static class NullSafeNavigator implements Map{ + final NavigableMap parent + final List path + + NullSafeNavigator(NavigableMap parent, List path) { + this.parent = parent + this.path = path + if (LOG.isWarnEnabled()) { + LOG.warn("Accessing config key '{}' through dot notation has known performance issues, consider using 'config.getProperty(key, targetClass)' instead.", path) + } + } + + Object getAt(Object key) { + getProperty(String.valueOf(key)) + } + + void setAt(Object key, Object value) { + setProperty(String.valueOf(key), value) + } + + @Override + int size() { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.size() + } + return 0 + } + + @Override + boolean isEmpty() { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.isEmpty() + } + return true + } + + boolean containsKey(Object key) { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap == null) return false + else { + return parentMap.containsKey(key) + } + } + + @Override + boolean containsValue(Object value) { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.containsValue(value) + } + return false + } + + @Override + Object get(Object key) { + return getAt(key) + } + + @Override + Object put(String key, Object value) { + throw new UnsupportedOperationException("Configuration cannot be modified"); + } + + @Override + Object remove(Object key) { + throw new UnsupportedOperationException("Configuration cannot be modified"); + } + + @Override + void putAll(Map m) { + throw new UnsupportedOperationException("Configuration cannot be modified"); + } + + @Override + void clear() { + throw new UnsupportedOperationException("Configuration cannot be modified"); + } + + @Override + Set keySet() { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.keySet() + } + return Collections.emptySet() + } + + @Override + Collection values() { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.values() + } + return Collections.emptySet() + } + + @Override + Set> entrySet() { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap != null) { + return parentMap.entrySet() + } + return Collections.emptySet() + } + + Object getProperty(String name) { + NavigableMap parentMap = parent.navigateSubMap(path, false) + if(parentMap == null) { + return new NullSafeNavigator(parent, ((path + [name]) as List).asImmutable()) + } else { + return parentMap.get(name) + } + } + + void setProperty(String name, Object value) { + NavigableMap parentMap = parent.navigateSubMap(path, true) + parentMap.setProperty(name, value) + } + + boolean asBoolean() { + false + } + + Object invokeMethod(String name, Object args) { + throw new NullPointerException("Cannot invoke method " + name + "() on NullSafeNavigator"); + } + + boolean equals(Object to) { + return to == null || DefaultGroovyMethods.is(this, to) + } + + Iterator iterator() { + return Collections.EMPTY_LIST.iterator() + } + + Object plus(String s) { + return toString() + s + } + + Object plus(Object o) { + throw new NullPointerException("Cannot invoke method plus on NullSafeNavigator") + } + + boolean is(Object other) { + return other == null || DefaultGroovyMethods.is(this, other) + } + + Object asType(Class c) { + if(c==Boolean || c==boolean) return false + return null + } + + String toString() { + return null + } + +// public int hashCode() { +// throw new NullPointerException("Cannot invoke method hashCode() on NullSafeNavigator"); +// } + } } diff --git a/grails-bootstrap/src/test/groovy/grails/config/ConfigMapSpec.groovy b/grails-bootstrap/src/test/groovy/grails/config/ConfigMapSpec.groovy index 4ed76c5f2eb..f977308bcd1 100644 --- a/grails-bootstrap/src/test/groovy/grails/config/ConfigMapSpec.groovy +++ b/grails-bootstrap/src/test/groovy/grails/config/ConfigMapSpec.groovy @@ -58,7 +58,7 @@ test.another = true configMap.getProperty('bar.two') == 'good4' } - def "should support flattening keys"() { + def "should support flattening keys - map syntax"() { given: NavigableMap configMap = new NavigableMap() when: @@ -66,9 +66,18 @@ test.another = true then: configMap.toFlatConfig() == ['a.b.c': 1, 'a.b.d': 2] } + def "should support flattening keys - dot syntax"() { + given: + NavigableMap configMap = new NavigableMap() + when: + configMap.a.b.c = 1 + configMap.a.b.d = 2 + then: + configMap.toFlatConfig() == ['a.b.c': 1, 'a.b.d': 2] + } @Issue('#9146') - def "should support hashCode()"() { + def "should support hashCode() - map syntax"() { given: NavigableMap configMap = new NavigableMap() when: @@ -77,7 +86,18 @@ test.another = true configMap.hashCode() == configMap.hashCode() } - def "should support flattening list values"() { + @Issue('#9146') + def "should support hashCode() - dot syntax"() { + given: + NavigableMap configMap = new NavigableMap() + when: + configMap.a.b.c = 1 + configMap.a.b.d = 2 + then:"hasCode() doesn't cause a Stack Overflow error" + configMap.hashCode() == configMap.hashCode() + } + + def "should support flattening list values - map syntax"() { given: NavigableMap configMap = new NavigableMap() when: @@ -90,8 +110,23 @@ test.another = true 'a.b.c[2]': 3, 'a.b.d': 2] } + + def "should support flattening list values - dot syntax"() { + given: + NavigableMap configMap = new NavigableMap() + when: + configMap.a.b.c = [1, 2, 3] + configMap.a.b.d = 2 + then: + configMap.toFlatConfig() == + ['a.b.c': [1, 2, 3], + 'a.b.c[0]': 1, + 'a.b.c[1]': 2, + 'a.b.c[2]': 3, + 'a.b.d': 2] + } - def "should support flattening to properties"() { + def "should support flattening to properties - map syntax"() { given: NavigableMap configMap = new NavigableMap() when: @@ -104,8 +139,23 @@ test.another = true 'a.b.c[2]': '3', 'a.b.d': '2'] } + + def "should support flattening to properties - dot syntax"() { + given: + NavigableMap configMap = new NavigableMap() + when: + configMap.a.b.c = [1, 2, 3] + configMap.a.b.d = 2 + then: + configMap.toProperties() == + ['a.b.c': '1,2,3', + 'a.b.c[0]': '1', + 'a.b.c[1]': '2', + 'a.b.c[2]': '3', + 'a.b.d': '2'] + } - def "should support cloning"() { + def "should support cloning - map syntax"() { given: NavigableMap configMap = new NavigableMap() configMap.a = [b: [c: [1, 2, 3], d: 2]] @@ -121,4 +171,22 @@ test.another = true !cloned.is(configMap) cloned == configMap } + + def "should support cloning - dot syntax"() { + given: + NavigableMap configMap = new NavigableMap() + configMap.a.b.c = [1, 2, 3] + configMap.a.b.d = 2 + when: + NavigableMap cloned = configMap.clone() + then: + cloned.toFlatConfig() == + ['a.b.c': [1, 2, 3], + 'a.b.c[0]': 1, + 'a.b.c[1]': 2, + 'a.b.c[2]': 3, + 'a.b.d': 2] + !cloned.is(configMap) + cloned == configMap + } } diff --git a/grails-bootstrap/src/test/groovy/grails/config/GrailsConfigSpec.groovy b/grails-bootstrap/src/test/groovy/grails/config/GrailsConfigSpec.groovy index 73842370280..2b84d493453 100644 --- a/grails-bootstrap/src/test/groovy/grails/config/GrailsConfigSpec.groovy +++ b/grails-bootstrap/src/test/groovy/grails/config/GrailsConfigSpec.groovy @@ -109,7 +109,7 @@ class GrailsConfigSpec extends Specification{ config.configMap == ['a.b.c.d':3, 'a.b.c.e.f':4, 'a.b.c.e':[f:4], 'a.b.c':[d:3, e:[f:4]], 'a.b.hello':'world', 'a.b':[c:[d:3, e:[f:4]], hello:"world"], a:[b:[c:[d:3, e:[f:4]], hello:"world"]]] } - def "should support removing values when key is set to null"() { + def "should support removing values when key is set to null - map syntax"() { given: CodeGenConfig config = new CodeGenConfig([a: [b: [c: [d: 1, e: 2]]]]) when: @@ -118,6 +118,16 @@ class GrailsConfigSpec extends Specification{ then: config.configMap == [a: [b: [c: 1]], 'a.b.c':1, 'a.b': [c: 1]] } + + def "should support removing values when key is set to null - dot syntax"() { + given: + CodeGenConfig config = new CodeGenConfig([a: [b: [c: [d: 1, e: 2]]]]) + when: + config.a.b = null + config.a.b.c = 1 + then: + config.configMap == [a: [b: [c: 1]], 'a.b.c':1] + } def "should support casting to Map"() { given: diff --git a/grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy b/grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy index c904b940155..3ad6701721e 100644 --- a/grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy +++ b/grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy @@ -14,6 +14,7 @@ import grails.util.Environment import grails.util.Holders import groovy.transform.CompileStatic import groovy.util.logging.Slf4j +import org.grails.config.NavigableMap import org.grails.config.PrefixedMapPropertySource import org.grails.config.PropertySourcesConfig import org.grails.core.exceptions.GrailsConfigurationException @@ -123,6 +124,18 @@ class GrailsApplicationPostProcessor implements BeanDefinitionRegistryPostProces return applicationContext.getResource(source); } }); + conversionService.addConverter(new Converter() { + @Override + public String convert(NavigableMap.NullSafeNavigator source) { + return null; + } + }); + conversionService.addConverter(new Converter() { + @Override + public Object convert(NavigableMap.NullSafeNavigator source) { + return null; + } + }); } def propertySources = environment.getPropertySources() def plugins = pluginManager.allPlugins diff --git a/grails-core/src/main/groovy/org/grails/config/NavigableMapPropertySource.groovy b/grails-core/src/main/groovy/org/grails/config/NavigableMapPropertySource.groovy index b76f1e7fead..412a25578a1 100644 --- a/grails-core/src/main/groovy/org/grails/config/NavigableMapPropertySource.groovy +++ b/grails-core/src/main/groovy/org/grails/config/NavigableMapPropertySource.groovy @@ -53,7 +53,7 @@ class NavigableMapPropertySource extends MapPropertySource { def value = super.getProperty(name) if (value instanceof OriginTrackedValue) { return ((OriginTrackedValue)value).value - } else if(value instanceof NavigableMap) { + } else if(value instanceof NavigableMap || value instanceof NavigableMap.NullSafeNavigator) { return null } return value diff --git a/grails-core/src/main/groovy/org/grails/plugins/AbstractGrailsPluginManager.java b/grails-core/src/main/groovy/org/grails/plugins/AbstractGrailsPluginManager.java index 302180a0668..64c3f13d62e 100644 --- a/grails-core/src/main/groovy/org/grails/plugins/AbstractGrailsPluginManager.java +++ b/grails-core/src/main/groovy/org/grails/plugins/AbstractGrailsPluginManager.java @@ -18,6 +18,7 @@ import grails.artefact.Enhanced; import grails.plugins.Plugin; import grails.plugins.PluginFilter; +import org.grails.config.NavigableMap; import grails.plugins.GrailsPlugin; import grails.plugins.GrailsPluginManager; import grails.plugins.GrailsVersionUtils; @@ -141,10 +142,22 @@ public void doRuntimeConfiguration(RuntimeSpringConfiguration springConfig) { if(autowireCapableBeanFactory instanceof ConfigurableListableBeanFactory) { ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)autowireCapableBeanFactory; ConversionService existingConversionService = beanFactory.getConversionService(); + ConverterRegistry converterRegistry; if(existingConversionService == null) { GenericConversionService conversionService = new GenericConversionService(); + converterRegistry = conversionService; beanFactory.setConversionService(conversionService); } + else { + converterRegistry = (ConverterRegistry)existingConversionService; + } + + converterRegistry.addConverter(new Converter() { + @Override + public Object convert(NavigableMap.NullSafeNavigator source) { + return null; + } + }); } checkInitialised(); for (GrailsPlugin plugin : pluginList) { diff --git a/grails-core/src/test/groovy/grails/config/PropertySourceConfigSpec.groovy b/grails-core/src/test/groovy/grails/config/PropertySourceConfigSpec.groovy index a440d7b47bd..ecf0e4c1e13 100644 --- a/grails-core/src/test/groovy/grails/config/PropertySourceConfigSpec.groovy +++ b/grails-core/src/test/groovy/grails/config/PropertySourceConfigSpec.groovy @@ -113,6 +113,94 @@ class PropertySourceConfigSpec extends Specification { config.'a.d' == 1 } + void "should support null safe navigation for getting"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig() + + expect: + config.a.b.c as Boolean == false + config.a.b.c as Map == null + } + + void "should support null safe navigation for setting"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig() + + when: + config.a.b.c = 1 + + then: + config.a == [b: [c: 1]] + config.'a.b' == [c: 1] + config.'a.b.c' == 1 + } + + void "should support merging values when map already exists"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig() + + when: + config.a.b.c = 1 + + then: + config.a == [b: [c: 1]] + config.'a.b' == ['c': 1] + config.'a.b.c' == 1 + + when: + config.merge([a: [d: 2]]) + + then: + config.a == [b: [c: 1], d: 2] + config.'a.b' == ['c': 1] + config.'a.b.c' == 1 + config.'a.d' == 2 + + when: + config.a.b.e = 3 + + then: + config.a == [b: [c: 1, e: 3], d: 2] + config.'a.b' == [c: 1, e: 3] + config.'a.b.c' == 1 + config.'a.d' == 2 + config.'a.b.e' == 3 + } + + void "should support setting map in null safe navigation"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig() + + when: + config.a.b.c = [d: 3, e: [f: 4]] + + then: + config.a == [b: [c: [d: 3, e: [f: 4]]]] + config.'a.b' == [c: [d: 3, e: [f: 4]]] + config.'a.b.c.d' == 3 + config.'a.b.c.e.f' == 4 + config.'a.b.c.e' == [f: 4] + config.'a.b.c' == [d: 3, e: [f: 4]] + } + + void "should support removing values when key is set to null"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig([a: [b: [c: [d: 1, e: 2]]]]) + + when: + config.a.b = null + config.a.b.c = 1 + + then: + config.a == [b: [c: 1]] + config.'a.b.c' == 1 + } + void "should support casting to boolean"() { given: @@ -127,4 +215,45 @@ class PropertySourceConfigSpec extends Specification { then: config as boolean == false } + + void "should support with"() { + given: + PropertySourcesConfig config = new PropertySourcesConfig() + + when: + config.a.b.c.with { + d = 1 + e = 2 + } + + then: + config.a == [b: [c: [d: 1, e: 2]]] + config.'a.b.c' == [d: 1, e: 2] + config.'a.b' == [c: [d: 1, e: 2]] + config.'a.b.c.d' == 1 + config.'a.b.c.e' == 2 + } + + void "null safe navigation should be supported without creating keys"() { + + given: + PropertySourcesConfig config = new PropertySourcesConfig() + def subconfigReference = config.a.b.c.d.e + + expect: + config.size() == 0 + + when: + subconfigReference.f.g = 1 + + then: + config.'a.b.c.d.e.f.g' == 1 + config.'a.b.c.d.e.f' == [g: 1] + config.'a.b.c.d.e' == [f: [g: 1]] + config.'a.b.c.d' == [e: [f: [g: 1]]] + config.'a.b.c' == [d: [e: [f: [g: 1]]]] + config.'a.b' == [c: [d: [e: [f: [g: 1]]]]] + config.'a' == [b: [c: [d: [e: [f: [g: 1]]]]]] + subconfigReference.f == [g: 1] + } } diff --git a/grails-core/src/test/groovy/grails/spring/GrailsPlaceholderConfigurerSpec.groovy b/grails-core/src/test/groovy/grails/spring/GrailsPlaceholderConfigurerSpec.groovy index 9c63e04dcf2..7b16125e13f 100644 --- a/grails-core/src/test/groovy/grails/spring/GrailsPlaceholderConfigurerSpec.groovy +++ b/grails-core/src/test/groovy/grails/spring/GrailsPlaceholderConfigurerSpec.groovy @@ -15,7 +15,7 @@ class GrailsPlaceholderConfigurerSpec extends Specification { Holders.setConfig(null) } - void "Test that property placeholder configuration works for simple properties"() { + void "Test that property placeholder configuration works for simple properties in map syntax"() { when:"A bean is defined with a placeholder" def application = new DefaultGrailsApplication() application.config.foo = [bar: "test"] @@ -33,6 +33,24 @@ class GrailsPlaceholderConfigurerSpec extends Specification { } + void "Test that property placeholder configuration works for simple properties in dot syntax"() { + when:"A bean is defined with a placeholder" + def application = new DefaultGrailsApplication() + application.config.foo.bar="test" + def bb = new BeanBuilder() + bb.beans { + addBeanFactoryPostProcessor(new GrailsPlaceholderConfigurer('${', application.config.toProperties())) + testBean(TestBean) { + name = '${foo.bar}' + } + } + def applicationContext = bb.createApplicationContext() + def bean = applicationContext.getBean(TestBean) + then:"The placeholder is replaced" + bean.name == "test" + + } + @Issue('GRAILS-9490') void "Test that property placeholder configuration doesn't throw an error if invalid placeholders are configured"() { when:"A bean is defined with a placeholder" diff --git a/grails-core/src/test/groovy/org/grails/config/NavigableMapPropertySourceSpec.groovy b/grails-core/src/test/groovy/org/grails/config/NavigableMapPropertySourceSpec.groovy index f1425c962dd..e55e0e7f164 100644 --- a/grails-core/src/test/groovy/org/grails/config/NavigableMapPropertySourceSpec.groovy +++ b/grails-core/src/test/groovy/org/grails/config/NavigableMapPropertySourceSpec.groovy @@ -23,7 +23,7 @@ import spock.lang.Specification */ class NavigableMapPropertySourceSpec extends Specification { - def "Ensure navigable maps are not returned from a NavigableMapPropertySource"() { + def "Ensure navigable maps are not returned from a NavigableMapPropertySource when using map syntax"() { given:"A navigable map" def map = new NavigableMap() map.foo = [bar: "myval"] @@ -37,4 +37,18 @@ class NavigableMapPropertySourceSpec extends Specification { ps.getNavigableProperty('foo') instanceof NavigableMap } + def "Ensure navigable maps are not returned from a NavigableMapPropertySource when using dot syntax"() { + given:"A navigable map" + def map = new NavigableMap() + map.foo.bar = "myval" + when:"A NavigableMapPropertySource is created" + def ps = new NavigableMapPropertySource("test", map) + then:"Nulls are returned for submaps" + map.keySet() == ['foo', 'foo.bar' ] as Set + ps.getPropertyNames() == ['foo.bar'] as String[] + ps.getNavigablePropertyNames() == ['foo' , 'foo.bar'] as String[] + ps.getProperty('foo') == null + ps.getNavigableProperty('foo') instanceof NavigableMap + + } } diff --git a/grails-core/src/test/groovy/org/grails/config/PropertySourcesConfigSpec.groovy b/grails-core/src/test/groovy/org/grails/config/PropertySourcesConfigSpec.groovy index f81be843b9e..85d52c358c3 100644 --- a/grails-core/src/test/groovy/org/grails/config/PropertySourcesConfigSpec.groovy +++ b/grails-core/src/test/groovy/org/grails/config/PropertySourcesConfigSpec.groovy @@ -41,7 +41,7 @@ class PropertySourcesConfigSpec extends Specification { config.one == 1 config.two == 2 config.three.four == 34 - !config?.four?.five + !config.four.five config.getProperty('one', String) == '1' config.getProperty('three.four', String) == '34' config.getProperty('three', String) == null diff --git a/grails-core/src/test/groovy/org/grails/config/YamlPropertySourceLoaderSpec.groovy b/grails-core/src/test/groovy/org/grails/config/YamlPropertySourceLoaderSpec.groovy index ea6028c29af..4fffbadf955 100644 --- a/grails-core/src/test/groovy/org/grails/config/YamlPropertySourceLoaderSpec.groovy +++ b/grails-core/src/test/groovy/org/grails/config/YamlPropertySourceLoaderSpec.groovy @@ -26,7 +26,7 @@ class YamlPropertySourceLoaderSpec extends Specification { config.one == 2 config.two == 3 config.three.four == 45 - !config.four?.five + !config.four.five config.getProperty('one', String) == '2' config.getProperty('three.four', String) == '45' config.getProperty('three', String) == null @@ -51,7 +51,7 @@ class YamlPropertySourceLoaderSpec extends Specification { then: "These will not be navigable due to false parseFlatKeys" config.one == 2 config.two == 3 - !config.four?.five + !config.four.five config.getProperty('one', String) == '2' config.getProperty('three.four', String) == '45' config.getProperty('three', String) == null diff --git a/grails-core/src/test/groovy/org/grails/transaction/ChainedTransactionManagerPostProcessorSpec.groovy b/grails-core/src/test/groovy/org/grails/transaction/ChainedTransactionManagerPostProcessorSpec.groovy index 5e919cac54f..154de90549f 100644 --- a/grails-core/src/test/groovy/org/grails/transaction/ChainedTransactionManagerPostProcessorSpec.groovy +++ b/grails-core/src/test/groovy/org/grails/transaction/ChainedTransactionManagerPostProcessorSpec.groovy @@ -62,7 +62,7 @@ class ChainedTransactionManagerPostProcessorSpec extends Specification { !(transactionManager instanceof ChainedTransactionManager) } - void "transactionManager bean should not get replaced when additional datasources aren't transactional"() { + void "transactionManager bean should not get replaced when additional datasources aren't transactional by map syntax"() { given: def bb = new BeanBuilder() def config = new PropertySourcesConfig() @@ -81,6 +81,25 @@ class ChainedTransactionManagerPostProcessorSpec extends Specification { !(transactionManager instanceof ChainedTransactionManager) } + void "transactionManager bean should not get replaced when additional datasources aren't transactional by dot syntax"() { + given: + def bb = new BeanBuilder() + def config = new PropertySourcesConfig() + config.dataSources.ds1.transactional = false + config.dataSources.ds2.transactional = false + bb.beans { + chainedTransactionManagerPostProcessor(ChainedTransactionManagerPostProcessor, config) + transactionManager(ChainedTransactionManagerTests.TestPlatformTransactionManager, "transactionManager") + transactionManager_ds1(ChainedTransactionManagerTests.TestPlatformTransactionManager, "transactionManager_ds1") + transactionManager_ds2(ChainedTransactionManagerTests.TestPlatformTransactionManager, "transactionManager_ds2") + } + when: + def applicationContext = bb.createApplicationContext() + def transactionManager = applicationContext.getBean("transactionManager") + then: + !(transactionManager instanceof ChainedTransactionManager) + } + void "transactionManager bean should get replaced when one of the additional datasources is transactional"() { given: diff --git a/grails-plugin-codecs/src/test/groovy/org/grails/web/codecs/HTMLCodecTests.groovy b/grails-plugin-codecs/src/test/groovy/org/grails/web/codecs/HTMLCodecTests.groovy index 96795019c85..f0455d5f7e8 100644 --- a/grails-plugin-codecs/src/test/groovy/org/grails/web/codecs/HTMLCodecTests.groovy +++ b/grails-plugin-codecs/src/test/groovy/org/grails/web/codecs/HTMLCodecTests.groovy @@ -21,7 +21,7 @@ class HTMLCodecTests { def getEncoderHtml() { def htmlCodec = new HTMLCodec() def grailsApplication = new DefaultGrailsApplication() - grailsApplication.config['grails.views.gsp.htmlcodec'] = 'html' + grailsApplication.config.grails.views.gsp.htmlcodec = 'html' grailsApplication.configChanged() htmlCodec.setGrailsApplication(grailsApplication) htmlCodec.afterPropertiesSet()