scalasbtdatanucleusmetamodel

How can SBT generate metamodel classes from model classes using DataNucleus?


How can I generate metamodel classes (like QClient, QProduct, QInvoice) from a persistence model classes (like Client, Product, Invoice) so that a JDOQL typesafe queries can be employed?

In particular, I'm interested on generating the metamodel classes and also run bytecode enhancement on persistence classes, via SBT and using DataNucleus with JDO annotations.


This question is related to
How can I run DataNucleus Bytecode Enhancer from SBT?


Solution

  • In a nutshell, you need to define functions in your build.sbt which perform the generation of the metamodel and bytecode enhancement, like shown below:

    def generateQueryEntities(sourcepath: File,
                              sources: Seq[File],
                              generated: File,
                              classes: File,
                              classpath: Seq[File],
                              javacOptions: Seq[String]): Seq[File] = {
      IO.createDirectory(generated)
      javaRunner(
        javaTool = Option("javac"),
        args =
          javacOptions ++
            (if(debug) Seq("-verbose") else Seq.empty[String]) ++
            Seq(
              "-sourcepath", sourcepath.absolutePath,
              "-s",  generated.absolutePath,
              "-d",  classes.absolutePath) ++
            sources.map(p => p.absolutePath),
        classpath = Option(classpath),
        cwd = Option(classes)
      )
      classes.listFiles.filter(f => f.isFile && (f.ext == "class"))
    }
    
    def enhanceSchema(classes: File, classpath: Seq[File]): Seq[File] = {
      javaRunner(
        mainClass = Option("javax.jdo.Enhancer"),
        args =
          (if(debug) Seq("-v") else Seq.empty[String]) ++
          Seq(
            "-pu", "code-generation",
            "-d",  classes.absolutePath),
        classpath = Option(classpath),
        cwd = Option(classes)
      )
      classes.listFiles.filter(f => f.isFile && (f.ext == "class"))
    }
    

    The next step consists on defining custom tasks for these functions. We need only genjdoql as shown below since the bytecode enhancement can be wired via sub-task manipulateBytecode. Notice that SBT 0.13.8 or greater is required.

    val genjdoql      = TaskKey[Seq[File]]("genjdoql",    "DataNucleus JDOQL Entities")
    

    Then you need to wire these functions in the build of your project or module, like shown below:

    lazy val model =
      project.in(file("model"))
        .settings(publishSettings:_*)
        .settings(librarySettings:_*)
        .settings(paranoidOptions:_*)
        .settings(otestFramework: _*)
        .settings(deps_resolvers:_*)
        //XXX .settings(deps_langtools:_*)
        .settings(deps_tagging:_*)
        .settings(deps_stream:_*)
        .settings(deps_database:_*)
        .settings(managedSources:_*)
        .settings(
          Seq(
            // generate JDOQL Entities
            genjdoql in Compile := {
              generateQueryEntities(
                sourcepath = (javaSource in Compile).value,
                sources = (unmanagedSources in Compile).value,
                generated = baseDirectory.value / "target" / scalav(scalaVersion.value) / "src_managed" / "main"  / "java",
                classes = (classDirectory in Compile).value,
                classpath = (managedClasspath in Compile).value.files,
                javacOptions = javacOpts :+ "-AqueryMode=PROPERTY"
              )},
            sourceGenerators in Compile <+= genjdoql in Compile,
            // prevent javac from running annotation processors
            javacOptions ++= Seq( "-proc:none" ),
            // perform bytecode enhancement
            manipulateBytecode in Compile := {
              val previous = (manipulateBytecode in Compile).value
              enhanceSchema(
                classes = (classDirectory in Compile).value,
                classpath =
                  (managedClasspath in Compile).value.files ++
                    (unmanagedResourceDirectories in Compile).value :+
                    (classDirectory in Compile).value)
              previous
            }
          ):_*)
        .dependsOn(util)
    

    For a complete example, please have a look at
    https://github.com/frgomes/poc-scala-datanucleus