jamesfredley commented on issue #16122:
URL: https://github.com/apache/grails-core/issues/16122#issuecomment-5245025245

   ### Shim feasibility: this one looks small and self-contained
   
   Following the compatibility-shim precedents in #16011 (restore a deprecated 
contract behind an opt-in) and #16101 (narrow, default-off behavioural flag), I 
looked at what restoring these two access forms would actually require. It 
appears to be a very small change, because the class has already kept most of 
the Map-like surface.
   
   The runtime class is `grails/util/Metadata.groovy`, which lives in the 
**`grails-gradle/model`** build rather than in `grails-core` - worth noting up 
front, since that is a separately published artifact that `grails-core` 
consumes.
   
   Its current shape:
   
   ```groovy
   @CompileStatic
   class Metadata {
   
       <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
           return this.propertyResolver.getProperty(key, targetType, 
defaultValue)
       }
   
       boolean containsKey(Object key) {
           return this.propertyResolver.containsProperty((String) key)
       }
   
       Object getOrDefault(Object key, Object defaultValue) {
           return getProperty(key.toString(), Object, defaultValue)
       }
   
       Object navigate(String... path) {
           return this.propertyResolver.getProperty(path.join('.').toString(), 
Object, null)
       }
   }
   ```
   
   So `containsKey(Object)` and `getOrDefault(Object, Object)` - both Map-style 
accessors - are still present and still delegate to the resolver. The class is 
not a `Map` (it does not implement `Map`), and what is missing is specifically:
   
   - `get(Object)` - which is what `Metadata.current.get('some.key')` binds to
   - a single-argument property read - which is what 
`Metadata.current.'some.key'` binds to
   
   Both have an exact one-line expression already in use elsewhere in the 
class, namely `getProperty(key.toString(), Object, defaultValue)`.
   
   Restoring them would therefore be roughly:
   
   ```groovy
   @Deprecated
   Object get(Object key) {
       return getProperty(key.toString(), Object, null)
   }
   
   @Deprecated
   Object propertyMissing(String name) {
       return getProperty(name, Object, null)
   }
   ```
   
   `propertyMissing` still works despite `@CompileStatic` on the class, because 
the failing call sites are dynamic Groovy in the *calling* plugin, so dispatch 
goes through the receiver's MetaClass at runtime.
   
   That would make both previously-working forms return the value or `null` 
again, rather than throwing, and it keeps the supported typed accessor 
`getProperty(key, Class, default)` as the documented path.
   
   ### Why this may be worth shimming rather than only documenting
   
   The two removed forms fail at **runtime**, not at build time, so an 
unchanged Grails 7 binary plugin resolves, loads, and boots before throwing on 
the first request that touches the code path - as `grails-web-console:7.1.0` 
does with a 500 on `GET /console`. Unlike the other Groovy-level breaks I have 
found in this sweep (#16123, #16126, #16128), this one does not require the 
plugin to be recompiled to be fixed; the framework can simply answer the call 
again.
   
   If restoring them unconditionally is not desired, the #16101 pattern would 
fit: keep them removed by default and allow opting in, so a legacy application 
can run today while plugin authors migrate to `getProperty(key, Class, 
default)`.
   
   I have not raised a PR for this - flagging the assessment in case it is 
useful.
   


-- 
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]

Reply via email to