In a recent Java project, I need to generate Java classes from an XML schema and an XML schema from a different set of Java classes. The underlying technology I use for these task is the Java Architecture for XML Binding (JAXB). The jdk comes with two tools, xjc and schemagen (schemagen currently produces a warning that says that it is planned to be replaced by javac in future versions of the jdk, so one should not rely on it. For now, I guess that is ok.). Xjc generates classes from a schema and schemagen generates a schema from a set of annoted classes.
Both generated artifacts might be subject to change, especially the schema. To adapt to that easily, generation is to happen during the build process. I recently started using Gradle, and the following build script is one of my first exercises with it. The solution rests upon the jaxb-xjc.jar which can be found in maven central. This jar comes with the xjc and schemagen classes. In the build script, I define ant tasks for executing these classes. The ant tasks used come from here. This leaves a straight-forward script (anonymized from my project):
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'application' mainClassName='my.package.MyMainClass' defaultTasks 'build' version = '1.0' sourceCompatibility = 1.7 schemaTargetDir = new File('generated-sources') repositories { mavenCentral() } configurations { xjc } dependencies { xjc group: 'com.sun.xml.bind', name: 'jaxb-xjc', version: '2.2.4-1' } task createDirs () { schemaTargetDir.mkdirs() } task xjc () { ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.xjc.asPath) ant.xjc( destdir: schemaTargetDir, schema: 'src/main/resources/MyInputSchema.xsd' ) } task schemagen () { ant.taskdef(name: 'schemagen', classname: 'com.sun.tools.jxc.SchemaGenTask', classpath: configurations.xjc.asPath) ant.schemagen( srcdir: new File('src/main/java/classes/to/generate/schema/from'), destdir: schemaTargetDir ) } xjc.dependsOn createDirs schemagen.dependsOn createDirs compileJava.dependsOn xjc, schemagen
Lines 01 through 23 are ‘business as usual’ with a global property and the xjc-dependency. The jaxb-xjc.jar includes both, schemagen and xjc, so a single dependency is sufficient. Lines 24 – 26 define a really simple task for building the necessary directory structure. Lines 28 to 35 and 37 to 44 do the real work. That is, defining the ant task and executing it with the appropriate option. Finally, some task dependency definitions in lines 26 to 48 and the script is done.
Update 16-12-2014: Many thanks to Paweł Grześ for suggesting a modified task definition (see comments). The following example from Pawel generates schemas only if the code has really changed:
task schemagen () { inputs.sourceDir('src/main/java/classes/to/generate/schema/from') outputs.dir('scr/main/resources/schema') doLast { ant.taskdef(name: 'schemagen', classname: 'com.sun.tools.jxc.SchemaGenTask', classpath: configurations.xjc.asPath) ant.schemagen( srcdir: 'src/main/java/classes/to/generate/schema/from', destdir: 'scr/main/resources/schema', includeAntRuntime: 'false' ) { schema(file: "mySchema.xsd", namespace: "http://www.myCompany.com") } } }
Update 01-08-2016: Lemurian N Mars pointed me to a jaxb plugin that is available for gradle and looks promising. Here is a build script that I modified slightly from Lemurian’s example (stripping Maven/Spring dependencies, streamlining to my example from above) that exemplifies the usage of xjc. I haven’t executed it (yet), so no guarantees that it actually works. Also, I am not sure how to execute schemagen with plugin. Anyways, here is the example:
buildscript { repositories { jcenter() } dependencies { classpath 'com.github.jacobono:gradle-jaxb-plugin:1.3.5' } } apply plugin: ‘java’ apply plugin: ‘com.github.jacobono.jaxb’ jar { baseName = ‘my-project’ version = ‘0.1.0’ } repositories { jcenter() } dependencies { jaxb ‘com.sun.xml.bind:jaxb-xjc:2.2.7-b41’ jaxb ‘com.sun.xml.bind:jaxb-impl:2.2.7-b41’ jaxb ‘javax.xml.bind:jaxb-api:2.2.7 } jaxb { xjc { xsdDir = 'src/main/resources/' generatePackage = 'generated-sources' } }
Awesome.. Thanks for sharing..
Thanks for the hint! That might be preferable to using ant tasks. great to see that schema generation is now a first class citizen in gradle. I think I’ll add this to the post (stripped of maven/spring references) and credit your name.
Here is a example which used to convert xsd to java without ant task only using the gradle plugins with java.
// XJC – Needs JDK 1.7/1.8 needs
buildscript {
repositories {
maven { url “https://repo.spring.io/libs-release” }
mavenLocal()
mavenCentral()
}
dependencies {
classpath ‘com.github.jacobono:gradle-jaxb-plugin:1.3.5’
}
}
apply plugin: ‘java’
apply plugin: ‘com.github.jacobono.jaxb’
jar {
baseName = ‘gs-rest-service’
version = ‘0.1.0’
}
repositories {
mavenLocal()
mavenCentral()
maven { url “https://repo.spring.io/libs-release” }
}
dependencies {
jaxb ‘com.sun.xml.bind:jaxb-xjc:2.2.7-b41’
jaxb ‘com.sun.xml.bind:jaxb-impl:2.2.7-b41’
jaxb ‘javax.xml.bind:jaxb-api:2.2.7’
//compile(“org.springframework.boot:spring-boot-starter-web”)
//testCompile(“junit:junit”)
}
jaxb {
xjc {
xsdDir = “.”
generatePackage = “com.gradletoxsd.example”
}
}
Thanks and good to know. I haden’t yet needed the functionality since the release of Java 8, so I did not run into the problem. All in all, this reads like a new blog post would be needed for explaining how that works with Java 8.
I have some further observations regarding Java 8, gradle and schema generation.
Java 8 does not have AnnotationProcessorFactory anymore, so the new JAXB libraries use javac compiler instead. It may have some significant impact onto your schema generation, because schemagen task must have proper classpath provided.
In case your annotated classes (for schema generation) reference some other classes (e.g. utilities) then those referenced classes (in compiled form) must be known for schemagen task.
I know it was not quite clear exaplanation :) Let’s see a Gradle example:
task schemagen () {
ant.taskdef(name: ‘schemagen’, classname: ‘com.sun.tools.jxc.SchemaGenTask’, classpath: configurations.xjc.asPath)
ant.schemagen(
srcdir: new File(‘src/main/java/classes/to/generate/schema/from’),
destdir: schemaTargetDir,
classpath: sourceSets.main.runtimeClasspath.asPath
)
}
schemagen.dependsOn compileJava, createDirs
processResources.dependsOn schemagen
There are 2 main differences:
– schemagen is performed AFTER compilation of classes.
– classpath is provided for schema generation
Hi Pawel,
once again, thanks for sharing :) For now, I’ll leave the post on 1.7 source compatibility.
Regards
Jörg
I would like to share my today’s experience.
If somebody needs to use a newer library for schema generation (or code generation) then the dependencies should be as follows:
configurations {
jaxb
}
dependencies {
jaxb ‘org.glassfish.jaxb:jaxb-core:2.2.11’
jaxb ‘org.glassfish.jaxb:jaxb-jxc:2.2.11’
jaxb ‘org.glassfish.jaxb:jaxb-xjc:2.2.11’
}
It seems to be important in case you would like to use Java 8 for compilation.
Hi Pawel,
thanks a lot, this is a very good suggestion! I updated the post and added your task definition. Thanks very much for sharing!
Regards
Jörg
Thanks Jörg for you excellent tutorial. It helped me a lot.
In my case I have extended you solution a little to use Gradle UP-TO-DATE tracking to generate schemas only if the code really changed. Here is an example:
task schemagen () {
inputs.sourceDir(‘src/main/java/classes/to/generate/schema/from’)
outputs.dir(‘scr/main/resources/schema’)
doLast {
ant.taskdef(name: ‘schemagen’, classname: ‘com.sun.tools.jxc.SchemaGenTask’, classpath: configurations.xjc.asPath)
ant.schemagen(
srcdir: ‘src/main/java/classes/to/generate/schema/from’,
destdir: ‘scr/main/resources/schema’,
includeAntRuntime: ‘false’
) {
schema(file: “mySchema.xsd”, namespace: “http://www.myCompany.com”)
}
}
}
Hi Pedro,
yes apparently that is possible. Have a look at the XJC ant task: https://jaxb.java.net/2.2.4/docs/xjcTask.html The gradle version should work in exactly the same way, albeit with a different syntax. Quoting from the documentation: “To compile more than one schema at the same time, use a nested element, which has the same syntax as . “.
Regards, Jörg
Hello. Is it possible to have a “schemaDir”, in order to translate multiple schemas in one task? You are using “schema: ‘src/main/resources/MyInputSchema.xsd'”, and only found the same solution in other tutorials. I would like to pass multiple schemas at once. Best, Pedro.
I have the link for the error and details what I am doing on http://stackoverflow.com/questions/21591636/schemagen-gradle-build-error . please take a look there.
Hi,
can you paste the problematic part of your build script? That way helping would be so much more easy :)
Hi,
I am new to gradle, trying to move maven project to gradle. In maven I am using maven plugin to generate schema which has multi excludes how do I do the similar thing in the above script. When I had multi exclude I get error duplicate parameter.
Thank you in advance.