Applying Groovy extensions in Grails produces MissingMethodException for String#toBoolean()
Asked Answered
I

1

6

Background

Groovy have the feature of adding methods to the existing classes, and I've found some interesting ones.

Then I discovered that I need to customize my Grails bootstrap to load them, so I add:

def init = { servletContext -> addExtensionModules() }

  def addExtensionModules() {

    Map<CachedClass, List<MetaMethod>> map = [:]
    ClassLoader classLoader = Thread.currentThread().contextClassLoader
    try {
      Enumeration<URL> resources = classLoader.getResources(MetaClassRegistryImpl.MODULE_META_INF_FILE)
      for (URL url in resources) {
        if (url.path.contains('groovy-all')) {
          // already registered
          continue
        }
        Properties properties = new Properties()
        InputStream inStream
        try {
          inStream = url.openStream()
          properties.load(inStream)
          GroovySystem.metaClassRegistry.registerExtensionModuleFromProperties(properties,
                            classLoader, map)
        }
        catch (IOException e) {
          throw new GroovyRuntimeException("Unable to load module META-INF descriptor", e)
        } finally {
          inStream?.close()
        }
      }
    }  catch (IOException ignored) {}
    map.each { CachedClass cls, List<MetaMethod> methods ->
    cls.setNewMopMethods(methods)
  }
}

And I add in my BuildConfig.groovy

compile ('ca.redtoad:groovy-crypto-extensions:0.2') {
  excludes 'groovy-all'
}

The Question

The problem is that now I cannot use the toBoolean() method of Groovy String:

groovy.lang.MissingMethodException: No signature of method: java.lang.String.toBoolean() is applicable for argument types: () values: [] Possible solutions: asBoolean(), asBoolean(), toFloat(), toDouble()

Since groovy is already registered, why the method is missing? I'm using Grails 2.2.4.

EDIT

Tested in a groovy 2.0.8 console, and the code works, so probably is something related to Grails.

@Grab('ca.redtoad:groovy-crypto-extensions:0.2')
@GrabExclude('org.codehaus.groovy:groovy-all')

addExtensionModules() //same method of BootStrap, ommited to make shorter.

def key = "password".toKey()
def ciphertext = "some plaintext".bytes.encrypt(key: key)
def x = new String(ciphertext.decrypt(key: key)).toBoolean()
println "S".toBoolean()
Imprinting answered 24/10, 2013 at 11:39 Comment(3)
@dmahapatro A challenge to you, my friend :-)Imprinting
Is there a bounty involved my dear friend? Kidding... Will look into it as soon as I am done with a meeting right now. ;)Josephson
question eligible for bounty in 2 days heheImprinting
J
7

Replace

map.each { CachedClass cls, List<MetaMethod> methods ->
    cls.setNewMopMethods(methods)
}

with

map.each { CachedClass cls, List<MetaMethod> methods ->
    //Add new MOP methods instead of set them as new
    cls.addNewMopMethods(methods) 
}

When new meta method is set in the CachedClass the existing extensions/meta methods are overriden by the only provided extension from the extension module. In this case, groovy-crypto-extension uses the below extension methods on String class

class java.lang.String=
[public static javax.crypto.spec.SecretKeySpec ca.redtoad.groovy.extensions.crypto.CryptoExtensionMethods.toKey(java.lang.String), 
 public static javax.crypto.spec.SecretKeySpec ca.redtoad.groovy.extensions.crypto.CryptoExtensionMethods.toKey(java.lang.String,java.util.Map)
] 

If those methods are set to the CachedClass, the existing methods are wiped out. So it has to be replace with adding them to the CachedClass. Hence, making toBoolean available on String class.

Challenge accepted and executed. You owe me a treat. (Gift Card is acceptable too). ;)

Josephson answered 24/10, 2013 at 15:16 Comment(1)
Yeah dude, I owe you one! I started debugging MetaClassRegistryImpl but had to leave the office, and probably will spent more time to figure it out :-) Thanks!Imprinting

© 2022 - 2024 — McMap. All rights reserved.