jdaugherty opened a new pull request, #16694: URL: https://github.com/apache/grails-core/pull/16694
Fixes #16675 ## Problem Groovy 5 resolves a String subscript on a `Map` as property access: at runtime `getAt(Object, String)` and `putAt(Object, String, Object)` win over the `Map` overloads, and the static compiler binds a String-keyed read the same way. Every Groovy object has the `properties` and `metaClass` properties, so for *any* `Map`, a `LinkedHashMap` included: | Expression | Groovy 4 | Groovy 5 | |---|---|---| | `map['properties']` | the entry | the meta-properties of the map object | | `map['properties'] = v` | map put | throws `ReadOnlyPropertyException` | | `map['metaClass'] = v` | map put | replaces the metaclass of the map | Under `@CompileStatic` the assignment is still dispatched at runtime by the class of the key, so a statically compiled `map[key] = value` fails the same way when `key` is `"properties"` (verified against Groovy 5.1.3, indy on and off). GORM for MongoDB decoded `Map` properties, embedded maps and nested documents with that subscript, so a document holding a GeoJSON `Feature`, whose `properties` member is part of the format, could not be loaded; the `BsonDocument` converter also read a nested key such as `firstKey` as a `BsonDocument` accessor. The data binders built the bound value of a `Map` property with the same subscript, so binding such a map, or an indexed key such as `attributes[properties]`, recorded a binding error instead of the entry. ## Fix `Map.get` / `Map.put` at every site where the key comes from a document or from the binding source: - `CodecExtensions`: the `BsonDocument` to `Map` converter and `MapCodec.decode` - `EmbeddedCollectionDecoder`: the `Map` branch - `GrailsWebDataBinder`: the bound map of a `Map` property, and the indexed persistent-instance path - `SimpleDataBinder`: the indexed `Map` property path - `AbstractStructuredBindingEditor.getPropertyValuesMap` Affects `8.0.x` and up; `7.x` is on Groovy 4. ## Documentation - `upgrading80x.adoc` ยง28.4, next to the existing Groovy 5 notes: the behaviour, that Grails stores and loads such keys, and that application code reads them with `get()`. - NOTEs in the data binding guide (Binding To Collections And Maps) and the GORM for MongoDB guide (Basic Collection Types). ## Testing New tests, each failing on `8.0.x` without the fix (with `ReadOnlyPropertyException`, or a missing entry) and passing with it: - `CodecExtensionsSpec`: the `BsonDocument` converter with `properties`, `metaClass`, `firstKey` and `class` keys; `MapCodec` decode; a GeoJSON-shaped round trip through `BsonDocumentWriter` / `BsonDocumentReader` - `EmbeddedCollectionDecoderSpec`; `BsonPersistentEntityCodecSpec` (entity round trip of a `Map` property) - `MapKeysNamedLikeBeanPropertiesSpec` in `grails-data-mongodb-core`, against a MongoDB container: the three scenarios of the issue's reproducer (save, assign on update, a document inserted with the driver), plus a dotted query on the nested key - `GrailsWebDataBinderSpec`, `GrailsWebDataBindingStructuredEditorSpec`, `CollectionBindingSpec`, `SimpleDataBinderSpec` Run locally: the specs above and `codeStyle` for `grails-data-mongodb-bson`, `grails-web-databinding` and `grails-databinding-core`. The full module suites are left to CI. Not in this PR: `DirtyCheckingMap`, the wrapper a loaded `Map` property comes back as, declares `parent`, `property`, `originalSize` and `assigned` accessors, so on Groovy 5 entries with those names are shadowed for subscript reads in application code (the `Map` API still reaches them). That is the remaining item from #16290. -- 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]
