Copilot commented on code in PR #16160:
URL: https://github.com/apache/grails-core/pull/16160#discussion_r3792744894
##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/ConfigurationBuilder.groovy:
##########
@@ -463,4 +456,196 @@ abstract class ConfigurationBuilder<B, C> {
protected void startBuild(Object builder, String configurationPath) {
// no-op
}
+ /**
+ * Handle ConversionFailedException - for enums, try case-insensitive
conversion
+ */
+ private Object handleConversionException(ConversionFailedException e,
Class argType, String propertyPathForArg, Object fallBackValue) {
+ if (argType.isEnum()) {
+ def value = propertyResolver.getProperty(propertyPathForArg,
String)
+ if (value != null) {
+ try {
+ return Enum.valueOf((Class) argType, value.toUpperCase())
+ } catch (IllegalArgumentException e2) {
+ throw new ConfigurationException("Invalid value for
setting [$propertyPathForArg]: $e.message", e)
+ }
+ }
+ else {
+ throw new ConfigurationException("Invalid value for setting
[$propertyPathForArg]: $e.message", e)
+ }
+ }
+ else {
+ ConverterNotFoundException converterNotFoundException =
findConverterNotFoundException(e)
+ if (converterNotFoundException != null) {
+ return
handleConverterNotFoundException(converterNotFoundException, argType,
propertyPathForArg, fallBackValue)
+ }
+ throw new ConfigurationException("Invalid value for setting
[$propertyPathForArg]: $e.message", e)
+ }
+ }
+
+ private static ConverterNotFoundException
findConverterNotFoundException(Throwable exception) {
+ Throwable cause = exception
+ while (cause != null) {
+ if (cause instanceof ConverterNotFoundException) {
+ return (ConverterNotFoundException) cause
+ }
+ cause = cause.getCause()
+ }
+ return null
+ }
+
+ /**
+ * Handle ConverterNotFoundException - for nested configuration types,
+ * try to instantiate and populate from Map. This handles Spring 7
compatibility where
+ * Spring can't auto-convert from LinkedHashMap to these types. This is
independent of the
+ * Groovy version and is required regardless of @Builder annotation
retention.
+ */
+ @CompileDynamic
+ private Object handleConverterNotFoundException(ConverterNotFoundException
e, Class argType, String propertyPathForArg, Object fallBackValue, Object
rawValue = null) {
+ if (rawValue == null) {
+ try {
+ // Use Object.class to prevent Spring's MapToMapConverter from
deep-converting values
+ rawValue = propertyResolver.getProperty(propertyPathForArg,
Object)
+ } catch (ConfigurationException e2) {
+ throw e2
+ } catch (Exception e2) {
+ throw new ConfigurationException("Cannot read configuration
for path [$propertyPathForArg]: $e2.message", e2)
+ }
+ }
+
+ if (rawValue instanceof Map) {
+ try {
+ Map<String, PropertyDescriptor> writableProperties = [:]
+ Introspector.getBeanInfo(argType).propertyDescriptors.each {
PropertyDescriptor property ->
+ if (property.name != 'metaClass' && property.writeMethod
!= null) {
+ writableProperties[property.name] = property
+ }
+ }
+
+ def instance = argType.getDeclaredConstructor().newInstance()
+ if (fallBackValue != null &&
argType.isInstance(fallBackValue)) {
+ // A map-backed settings type carries arbitrary entries as
well as declared
+ // properties, so the inherited entries have to come
across too or overriding
+ // one nested value would silently drop the rest.
+ if (instance instanceof Map && fallBackValue instanceof
Map) {
+ ((Map) instance).putAll((Map) fallBackValue)
+ }
+ writableProperties.values().each { PropertyDescriptor
property ->
+ if (property.readMethod != null &&
property.readMethod.parameterCount == 0) {
+ Object fallbackPropertyValue =
property.readMethod.invoke(fallBackValue)
+ property.writeMethod.invoke(instance,
[fallbackPropertyValue] as Object[])
+ }
+ }
+ }
+
+ boolean mapBacked = instance instanceof Map
+ Set<String> resolvedProperties = [] as Set<String>
+ ((Map) rawValue).each { key, val ->
+ String propertyName = key.toString()
+ PropertyDescriptor property =
writableProperties[propertyName]
+ if (property != null) {
+ Object fallBackPropertyValue =
getFallBackValue(fallBackValue, propertyName)
+ Object value = resolveMapValue(property.propertyType,
"$propertyPathForArg.$propertyName", fallBackPropertyValue, val)
+ property.writeMethod.invoke(instance, [value] as
Object[])
+ resolvedProperties.add(propertyName)
+ return
+ }
+ int nestedPropertySeparator = propertyName.indexOf('.')
+ if (nestedPropertySeparator > 0) {
+ String nestedPropertyName = propertyName.substring(0,
nestedPropertySeparator)
+ PropertyDescriptor nestedProperty =
writableProperties[nestedPropertyName]
+ if (nestedProperty != null) {
+ if (resolvedProperties.add(nestedPropertyName)) {
+ Object fallBackPropertyValue =
getFallBackValue(fallBackValue, nestedPropertyName)
+ Object value =
resolveMapValue(nestedProperty.propertyType,
"$propertyPathForArg.$nestedPropertyName", fallBackPropertyValue, val)
+ nestedProperty.writeMethod.invoke(instance,
[value] as Object[])
+ }
+ return
+ }
+ }
+ // Types that are themselves a Map (HibernateSettings
extends LinkedHashMap, for
+ // example) exist precisely to carry arbitrary keys such
as hibernate.hbm2ddl.auto,
+ // so an entry that is not a declared bean property
belongs in the map rather than
+ // being rejected. Only types with a fixed set of
properties reject unknown keys.
+ if (mapBacked) {
+ ((Map) instance).put(key, val)
+ return
+ }
Review Comment:
When binding unknown keys into a Map-backed settings type, the code stores
the original Map key object (`key`) instead of the normalized `String
propertyName`. If the raw map uses non-String keys (e.g. GString/CharSequence),
the resulting settings map may not behave as expected (lookups by String can
fail), despite earlier logic converting keys to String for property matching
and error messages.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]