Reputation: 21
So I have a bunch of compiled case classes in a .jar. I want to load and iterate over all of them and for each case class generate an Avro schema using scalavro. The scalavro AvroType expects a TypeTag, so essentially my question is how to appropriately reflect TypeTags from external case classes in a jar.
import java.net.URL
import com.gensler.scalavro.types.AvroType
import org.clapper.classutil.ClassFinder
import java.io.File
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
import scala.reflect.runtime.universe._
import scala.reflect.runtime.{ universe => ru }
object scalaAvroGen extends App {
val jarLocation = "someCaseClasses.jar"
val classpath = List(jarLocation).map(new File(_))
val classLoader = new URLClassLoader(Array[URL](new File(jarLocation).toURI.toURL), this.getClass().getClassLoader())
val finder = ClassFinder(classpath)
val classes = finder.getClasses.filterNot(_.isFinal)
classes.foreach {
loadedClass => {
val typeTag = typeToTypeTag(getType(classLoader.loadClass(loadedClass.name)))
val avroSchema = AvroType.apply(typeTag).schema()
println(avroSchema)
}
}
def getType[T](clazz: Class[T])(implicit runtimeMirror: ru.Mirror) =
runtimeMirror.classSymbol(clazz).toType
def typeToTypeTag[T](tpe: Type): TypeTag[T] = TypeTag.synchronized {
val mirror = scala.reflect.runtime.currentMirror
TypeTag(mirror, new reflect.api.TypeCreator {
def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
tpe.asInstanceOf[U#Type]
}
})
}
}
Example case class in the compiled .jar:
case class SimpleScalaAvroObject(version: Int, name: String)
Currently when I attempt to run this code I get the following error:
Error:(20, 39) type mismatch; found : reflect.runtime.universe.TypeTag[Nothing] required: reflect.runtime.universe.TypeTag[T] Note: Nothing <: T, but trait TypeTag is invariant in type T. You may wish to investigate a wildcard type such as `_ <: T`. (SLS 3.2.10)
val avroSchema = AvroType.apply(typeTag).schema()
^
Now I'm sure this is not my only issue, I've spent the last two days throwing everything at this. At one point I was getting class not found exceptions on SimpleScalaAvroObject so I'm probably not even reflecting correctly.
I'll eventually annotations to the case classes I actually care about but for now this is really just a PoC.
Please note up until a few months ago I was a C# dev so sorry for this mangled mess of Scala.
Thanks!
Upvotes: 2
Views: 639
Reputation: 16387
You can use another project avro4s (disclaimer: I wrote this), to generate the schemas at compile time rather than runtime, and any type tag issues you have should go away (as the compiler has access to more information that what is present at runtime).
Very simple to use,
case class SimpleScalaAvroObject(version: Int, name: String)
// this schema is of type org.apache.avro.Schema
val schema = AvroSchema[SimpleScalaAvroObject]
Upvotes: 2