Java annotation, scala object and javassist don't get along well
[Update: in the next paragraph, "showstopper" is clearly too strong - put it in my bad english level. The title is perhaps better, I wanted to say that things didn't came along as well as I hoped initially. In no case I wanted to sound over-pessimistic. Actually, the workaround is rather simple : I can use a direct call to the look-up service of T5 IOC each time I need a named service in place of the direct injection of that service.]
In my quest of using Tapestry 5 with a Scala back-end, I believe I fall on a showstopper. I seems that Javassist doesn't see annotation on method parameter in Scala object, although the annotation (being marked with Runtime retention) is seen with the Java reflexion API.
I tested with both Scala 2.7.3 and 2.8.0-nigtly, and both Javassist 3.9.0.GA and 3.10 - the behaviour is consistent in all combinations.
In fact, what seems to happens is that when one defines a Scala object "Foo.scala", scalac generates two classes : "Foo.class" that is a kind of singleton facade and initializer, and "Foo$.class" that is the actual class.
When the java reflexion API is used, I think that scala delegates the call in the rigth way. The problem seems to be that Javassist directly read the bytecode of "Foo.class", and doesn't see the annotation - it seems to be only on the method in "Foo$.class".
You can you see this behaviour like that:
Make a simple Java annotation, and compile it, something like @InjectService in Tapestry5-annotation package:
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface InjectService
{
String value();
}
Create a scala class that make use of that annotation and test it visibility thanks to Java reflexion API and Javassist :
import org.apache.tapestry5.ioc.annotations.InjectService
import javassist._
import javassist.runtime._
object ObjectTestAnnotation {
def methodWithParamAnnot(@InjectService("service id") s:String) = s
}
class ClassTestAnnotation {
def methodWithParamAnnot(@InjectService("service id") s:String) = s
}
object Main {
def main(args: Array[String]) {
/*
* Using Java reflexion API
*/
val methodWithParamAnnot = ObjectTestAnnotation.
getClass.getMethod("methodWithParamAnnot", classOf[String])
val annotations = methodWithParamAnnot.getParameterAnnotations
println("* with java reflexion API, on ObjectTestAnnotation")
annotations.foreach(_.foreach( x => println(x)))
// print:
// * with java reflexion API, on ObjectTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value=service id)
val methodWithParamAnnot1 = classOf[ClassTestAnnotation].
getMethod("methodWithParamAnnot", classOf[String])
val annotations1 = methodWithParamAnnot1.getParameterAnnotations
println("* with java reflexion API, on ClassTestAnnotation")
annotations1.foreach(_.foreach( x => println(x)))
// print:
// * with java reflexion API, on ClassTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value=service id)
/*
* Using Javassist
*/
val cp = ClassPool.getDefault()
val ctString = cp.get("java.lang.String")
val methodWithParamAnnot2 = cp.get("test.ObjectTestAnnotation")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations2 = methodWithParamAnnot2.getParameterAnnotations
println("* with javassist, on ObjectTestAnnotation")
annotations2.foreach(_.foreach( x => println(x)))
// print (AND THAT'S BAD, the annotation is not seen !)
// * with javassist, on ObjectTestAnnotation
val methodWithParamAnnot2_bis = cp.get("test.ObjectTestAnnotation$")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations2_bis = methodWithParamAnnot2_bis.getParameterAnnotations
println("* with javassist, on ObjectTestAnnotation$")
annotations2_bis.foreach(_.foreach( x => println(x)))
// print:
// * with javassist, on ObjectTestAnnotation$
// @org.apache.tapestry5.ioc.annotations.InjectService(value="service id")
val methodWithParamAnnot3 = cp.get("test.ClassTestAnnotation")
.getDeclaredMethod("methodWithParamAnnot",Array(ctString))
val annotations3 = methodWithParamAnnot3.getParameterAnnotations
println("* with javassist, on ClassTestAnnotation")
annotations3.foreach(_.foreach( x => println(x)))
// print:
// * with javassist, on ClassTestAnnotation
// @org.apache.tapestry5.ioc.annotations.InjectService(value="service id")
println("done")
}
}
Cry ! Because, of course, Tapestry 5 uses these annotations in such a way that a bug here make the whole thing unusable - the core of T5-IOC container relies on them.
I would have to see if I can use a class in place of the object...
