Skip to content

ctzen/jpamodelexp

Repository files navigation

jpamodelexp

Not able to use the JPA Static Metamodel Generator Annotation Processor because your entity classes are written in Groovy to avoid the boilerplate getters and setters?

Here is an exporter that can generate JPA Static Metamodel source files from Groovy classes (or any language that compiles to Java classes).

Usage (Gradle)

build.gradle.kts (incomplete, just the relevant parts)

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath("com.ctzen.jpamodelexp:jpamodelexp:<version>")
    }
}

val jpaModelExport by tasks.registering {
    dependsOn(tasks.classes)
    outputs.dir("src/generated")
    doLast {

        // 1. Create the class loader.
        //    Get the appropriate gradle compile task; compileGroovy in this example.
        val compileTask = tasks.compileGroovy.get()
        //    The rest of step 1 is more or less boiler plate.
        val urls = mutableListOf<URL>(compileTask.destinationDir.toURI().toURL())
        compileTask.classpath.forEach{ urls.add(it.toURI().toURL()) }
        val classLoader = URLClassLoader(urls.toTypedArray(), Thread.currentThread().contextClassLoader)

        // 2. Scan packages for annotated JPA classes.
        val scanner = AnnotatedJpaClassesPackageScanner(classLoader)
        scanner.addPackages("org.acme.entity", "org.acme.other.entity")
        val jpaClasses = scanner.scan()

        // 3. Build a JpaMetamodel.
        val jmb = AnnotatedClassesJpaMetamodelBuilder(classLoader)
        //    Add the classes gathered from step 2.
        jmb.addAnnotatedClasses(jpaClasses)
        //    And/or add JPA class names directly.
        jmb.addAnnotatedClassNames("org.acme.entity.Foo", "org.acme.entity.Bar")
        val jpaMetamodel = jmb.build()

        // 4. Export JPA static metamodel source files.
        val exporter = JpaModelExporter(file("src/generated"), jpaMetamodel)
        exporter.export()
    }
}
$ ./gradlew jpaModelExport

Look under src/generated for the generated metamodel source files.

Knobs

JpaModelExporter#setLineSep(String) sets the line separator of the generated metamodel source files; if you dislike the default System.getProperty("line.separator").

AnnotatedJpaClassesPackageScanner#setUrlProtocols(UrlProtocol...) controls what package resources to scan. AnnotatedJpaClassesPackageScanner uses ClassLoader#getResources() to explore the packages and is only capable of handling file:, and jar:file: protocols. By default, only the file: protocol is enabled.

Export Filters

JpaModelExporter essentially reverse engineers all the ManagedType from a Metamodel to generate the static metamodel source files.

Filters may be specified to control which JPA static metamodels to export.

JpaModelExporter#filterPackages(String) export classes in the specified packages and sub-packages. e.g. exporter.filterPackages("org.acme.entity")

JpaModelExporter#filterClasses(String) export specific classes. e.g. exporter.filterPackages("org.acme.entity.Foo")

JpaModelExporter#filter(Pattern) export classes with canonical names matching the specified regex pattern.

Multiple filters may be specified by calling the filter functions repeatedly. Classes that matches any of the filters are exported.

Caveats

  • The static metamodels exported are not identical syntatically to those generated by the annotation processor. However, they should be identical semantically, i.e. all the parts should be there but not in the exact order.

  • If a @MappedSuperclass has no concrete @Entity, its static metamodel class will not be generated because its ManagedType is not present in the Metamodel.

  • Same for @Embeddable that are not @Embedded.

  • Since the exporter wires up a Hibernate Session Factory to get to its Metamodel, it is probably too heavy to invoke for every build. The recommendation is to source control the generated metamodel source files, and invoke the exporter only when entity classes changed.

Building and Testing

$ ./gradlew clean build     # build and test the exporter (pure Java)
$ ./gradlew testClasses     # make sure the generated metamodels compiles

$ cd test-projects          # build and test the test projects (Groovy entity classes)
$ ./gradlew clean build     # the exporter main project must be built and tested before
                            # the test projects because the metamodels generated in the
                            # main project are used for verifications

See DEVELOPMENT.txt