21

We were trying to generate JAXB classes using gradle, xsd and xjc, and JAXB classes should have XmlRootElement annotations so it could be used to expose as web service reponse. We were following this link http://azagorneanu.blogspot.com/2011/09/configure-maven-to-generate-classes.html, it helped in great deal but we were unable to find one particular example with gradle only. So we figured out few things what we will share as an answer.

Aamir
  • 597
  • 1
  • 4
  • 24

7 Answers7

13

build.gradle should look like below

    buildscript {
    repositories {
    mavenCentral()
        jcenter()
    }
    dependencies {
        classpath "net.saliman:gradle-cobertura-plugin:2.2.4"
        classpath 'com.github.jacobono:gradle-jaxb-plugin:1.3.5'

    }
}
apply plugin: 'com.github.jacobono.jaxb'
dependencies {
    jaxb 'com.sun.xml.bind:jaxb-xjc:2.2.7'
    jaxb "org.jvnet.jaxb2_commons:jaxb2-basics-ant:0.6.5"
    jaxb "org.jvnet.jaxb2_commons:jaxb2-basics:0.6.4"
    jaxb "org.jvnet.jaxb2_commons:jaxb2-basics-annotate:0.6.4"
}
configurations {
    jaxb
}
task jaxb(){
    description 'Converts xsds to classes'
    def jaxbTargetDir = file("generated")
    doLast {
    jaxbTargetDir.mkdirs()
    ant.taskdef(name: 'xjc', classname: 'org.jvnet.jaxb2_commons.xjc.XJC2Task', classpath: configurations.jaxb.asPath)
    ant.jaxbTargetDir = jaxbTargetDir 
    ant.xjc(destdir: '${jaxbTargetDir}', package: 'com.sample.jaxbclasses', schema:'generated/schema.xsd', binding:'generated/binding.xjb', extension:'true'){
        arg(value: "-Xannotate")
        }
    }
}

schema.xsd

    <xs:element name="user" type="user" />
    <xs:element name="userList" type="userList" />

    <xs:complexType name="user">
        <xs:all>
            <xs:element name="id" type="xs:long" minOccurs="0" />
            <xs:element name="name" type="xs:string" />
            <xs:element name="registrationDate" type="xs:dateTime" />
        </xs:all>
    </xs:complexType>

    <xs:complexType name="userList">
        <xs:sequence>
            <xs:element name="user" type="user" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>

</xs:schema>

binding.xjb

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:annox="http://annox.dev.java.net" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
    version="2.1">
    <jaxb:globalBindings>
        <!-- Use java.util.Calendar instead of javax.xml.datatype.XMLGregorianCalendar for xs:dateTime -->
        <jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
                parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime" 
                printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />

        <!-- Force all classes implements Serializable -->
        <xjc:serializable uid="1" />
    </jaxb:globalBindings>

    <!-- Annotate the following classes with XmlRootElement -->
    <jaxb:bindings schemaLocation="schema.xsd" node="/xs:schema">
        <jaxb:bindings node="xs:complexType[@name='user']">
            <annox:annotate>
                <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="user" />
            </annox:annotate>
        </jaxb:bindings>
        <jaxb:bindings node="xs:complexType[@name='userList']">
            <annox:annotate>
                <annox:annotate annox:class="javax.xml.bind.annotation.XmlRootElement" name="userList" />
            </annox:annotate>
        </jaxb:bindings>
    </jaxb:bindings>
</jaxb:bindings>

below binding.xjb could be used as well

<?xml version="1.0"?>
<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jxb:extensionBindingPrefixes="xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <jxb:bindings schemaLocation="schema.xsd" node="/xs:schema">
    <jxb:globalBindings>
      <xjc:simple />
    </jxb:globalBindings>
  </jxb:bindings>
</jxb:bindings>

Now you can run the task 'jaxb', All set. Cheers!

User.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.7 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2017.01.26 at 11:59:18 AM EST 
//


package com.sample.jaxbclasses;

import java.io.Serializable;
import java.util.Calendar;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;


/**
* <p>Java class for user complex type.
* 
 * <p>The following schema fragment specifies the expected content contained within this class.
* 
 * <pre>
* &lt;complexType name="user">
*   &lt;complexContent>
*     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
*       &lt;all>
*         &lt;element name="id" type="{http://www.w3.org/2001/XMLSchema}long" minOccurs="0"/>
*         &lt;element name="name" type="{http://www.w3.org/2001/XMLSchema}string"/>
*         &lt;element name="registrationDate" type="{http://www.w3.org/2001/XMLSchema}dateTime"/>
*       &lt;/all>
*     &lt;/restriction>
*   &lt;/complexContent>
* &lt;/complexType>
* </pre>
* 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "user", propOrder = {

})
@XmlRootElement(name = "user")
public class User
    implements Serializable
{

    private final static long serialVersionUID = 1L;
    protected Long id;
    @XmlElement(required = true)
    protected String name;
    @XmlElement(required = true, type = String.class)
    @XmlJavaTypeAdapter(Adapter1 .class)
    @XmlSchemaType(name = "dateTime")
    protected Calendar registrationDate;

    /**
     * Gets the value of the id property.
     * 
     * @return
     *     possible object is
     *     {@link Long }
     *     
     */
    public Long getId() {
        return id;
    }

    /**
     * Sets the value of the id property.
     * 
     * @param value
     *     allowed object is
     *     {@link Long }
     *     
     */
    public void setId(Long value) {
        this.id = value;
    }

    /**
     * Gets the value of the name property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the value of the name property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setName(String value) {
        this.name = value;
    }

    /**
     * Gets the value of the registrationDate property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public Calendar getRegistrationDate() {
        return registrationDate;
    }

    /**
     * Sets the value of the registrationDate property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setRegistrationDate(Calendar value) {
        this.registrationDate = value;
    }

}
Aamir
  • 597
  • 1
  • 4
  • 24
9
group 'com.example'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8
targetCompatibility = 1.8


repositories {
    mavenCentral()
}

project.ext {
    jaxbTargetDir = file("src/generated/java")

}


configurations {
    xsd2java
}

dependencies {
    xsd2java "com.sun.xml.bind:jaxb-xjc:2.2.6"
    xsd2java "com.sun.xml.bind:jaxb-impl:2.2.6"
}

task xsd2java() {

    doLast {
        jaxbTargetDir.mkdirs()

        ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.xsd2java.asPath)
        ant.jaxbTargetDir = jaxbTargetDir


        ant.xjc(
                destdir: '${jaxbTargetDir}',
                package: 'com.example.request',
                schema: 'src/main/resources/XMLreq.xsd'
        )

        ant.xjc(
                destdir: '${jaxbTargetDir}',
                package: 'com.example.response',
                schema: 'src/main/resources/XMLres.xsd'
        )

    }
}
compileJava.dependsOn xsd2java
grep
  • 4,615
  • 10
  • 48
  • 96
  • In case you have multple .xsd files, one reference to these other, you need to add this `System.setProperty('javax.xml.accessExternalSchema', 'all')` to your `doLast()` – tuan.dinh Aug 19 '20 at 02:37
4

My version use gradle native feature to generate jaxbclasses.

Optionally, if case your schema depends on external xsd(s), use "Oasis Catalog" technique to resolve external XSD locally. Also in this case disable XML schema validations to prevent validation errors.

Optionally you can adjust your jaxbclasses with a custom jaxb binding. (Jaxb-bindings.xjb)

Basically is a gradle custom task, which invoke the XJCTask ant task available in Java VM, in my example libraries are from Java 8. Task name is "generateSources", and need to be adjusted to your schema location.

configurations {
    jaxb // Only for generation purpose
}

dependencies {
    jaxb  'javax.xml.bind:jaxb-api:2.2.11' 
    jaxb  'com.sun.xml.bind:jaxb-xjc:2.2.11'    
    jaxb  'com.sun.xml.bind:jaxb-impl:2.2.11'
    jaxb  'com.sun.xml.bind:jaxb-osgi:2.2.11'
}

task generateSources() {
    doLast {
        def jaxbTargetDir = file("$buildDir/generated/src/main/java")

        if (!jaxbTargetDir.exists()) {
            jaxbTargetDir.mkdirs()
        }

        ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.jaxb.asPath)

        ant.xjc(
                destdir: "${jaxbTargetDir}",
                schema: "${projectDir}/src/main/resources/MySchema.xsd",
                binding: "${projectDir}/src/main/resources/jaxb-bindings.xjb",
                catalog: "${projectDir}/src/main/resources/catalog.xml",
                removeOldOutput: 'yes', extension: 'true'
        )
                {
                    arg(line: '-nv -disableXmlSecurity')
                }
    }
}

In case you need catalog create a file "catalog.xml" which might look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog
    PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
           "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
    <system systemId="http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/xenc-schema.xsd" uri="xenc-schema.xsd" />
    <system systemId="http://www.w3.org/TR/xmlenc-core/xenc-schema.xsd" uri="xenc-schema.xsd" />
    <system systemId="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd" uri="xmldsig-core-schema.xsd" />
</catalog>

For jaxbinding

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <globalBindings>
        <xjc:javaType
            adapter="org.gazpachoquest.sample.JodaLocalDateTimeConverter"
            name="org.joda.time.LocalDateTime" xmlType="xs:dateTime" />
    </globalBindings>
</bindings>

If in addition to jaxb generation, you need to include those classes to build. You need to adjust gradle source layout, and dependencies.

apply plugin: 'java'

def generatedSourcesOutput = "$buildDir/generated/main/java"

sourceSets {
    main {
        java.srcDirs "$generatedSourcesOutput"
    }
}

configurations {
    jaxb
}

dependencies {
    jaxb  'javax.xml.bind:jaxb-api:2.2.11' 
    jaxb  'com.sun.xml.bind:jaxb-xjc:2.2.11'    
    jaxb  'com.sun.xml.bind:jaxb-impl:2.2.11'
    jaxb  'com.sun.xml.bind:jaxb-osgi:2.2.11'

    compile 'com.sun.xml.bind:jaxb-xjc:2.2.11'
    compile 'com.sun.xml.bind:jaxb-impl:2.2.11'
    compile 'javax.xml.bind:jaxb-api:2.2.11'
}

compileJava {
    dependsOn generateSources
}

That's all!

2

Gradle configuration for using gradle-jaxb-plugin

The commented out values in the xjc configuration are the default values - change if required.

buildscript {
    repositories gradle.repos

    dependencies {
    }
}

plugins {
    id "org.openrepose.gradle.plugins.jaxb" version "2.5.0"
    id 'groovy'
    id 'java'
    id "org.springframework.boot" version "2.1.2.RELEASE"
    id 'checkstyle'
}

apply plugin: 'io.spring.dependency-management'
apply plugin: "org.openrepose.gradle.plugins.jaxb"
apply plugin: 'idea'
apply plugin: 'maven-publish'

repositories gradle.repos

configurations {
    jaxb
    codeq
    compile.exclude module: "spring-boot-starter-tomcat"
}

jaxb {
    xjc {
        //taskClassname        = 'com.sun.tools.xjc.XJC2Task'
        //xsdDir               = "${project.projectDir}/src/main/resources/schema"
        generateEpisodeFiles = false
        generatePackage      = 'com.mycompany.mypackage'
        destinationDir       = "${buildDir}/generated/src/main/java"
        args                 = ["-Xannotate"]
    }
}

compileJava.dependsOn {
    'xjc'
}

sourceSets {
    main {
        java {
            srcDirs jaxb.xjc.destinationDir
        }
        resources {
            srcDirs 'src/main/resources'
        }
    }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudDependenciesVersion}"
    }
}

dependencies {

    // Jaxb dependencies
    jaxb group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: jaxbxjcVersion
    jaxb 'org.jvnet.jaxb2_commons:jaxb2-basics-annotate:1.0.4'
    jaxb 'org.slf4j:slf4j-log4j12:1.7.25'
    implementation group: 'javax.xml.bind', name: 'jaxb-api', version: jaxbApiVersion
}

and gradle.properties file

# JAXB Processing Properties
jaxbPluginVersion = 2.5.0
jaxbxjcVersion = 2.3.2
jaxbApiVersion = 2.3.1

and to link it with Intellij add the following to your build.gradle file

idea.module.iml {
    whenMerged {
        dependsOn jaxb
    }
}
sweetfa
  • 4,810
  • 2
  • 39
  • 54
1

I've been using the Spring Boot Producing a SOAP web service guide for reference. Here's the link to the build.gradle file in GitHub.

https://github.com/spring-guides/gs-producing-web-service/blob/master/complete/build.gradle

Bienvenido David
  • 3,388
  • 1
  • 21
  • 14
0

Here is a solution that works for me with Java 11 / Gradle 6. After updating the build system on one of my projects recently I found some issues using XJC via Ant tasks in Gradle - this approach just uses plain Gradle, without Ant.

UPDATE: Using the GlassFish implementation avoids issues with Sun internal dependencies, as per this question

sourceSets {

    generated {
        java.srcDir "$generated_dir"
    }
}

dependencies {

    compile sourceSets.generated.output

    // Generated code depends on the JAXB API, which is removed from base Java in JDK 11
    compile "org.glassfish.jaxb:jaxb-runtime:2.3.3"
    generatedCompile "org.glassfish.jaxb:jaxb-runtime:2.3.3"
}


// XJC tasks

// JAXB configuration holds classpath for running the JAXB XJC compiler
configurations {
    jaxb
}

dependencies {

    jaxb "org.glassfish.jaxb:jaxb-xjc:2.3.3"
}

// Cookie cutter function for defining multiple XJC tasks
// (not necessary if you only have a single task)!
def addXjcTask(taskName, schema, pkg, dest) {

    // If you haven't already, create the generated output dir before running XJC or it will fail
    file(dest).mkdirs()

    // The main XJC task, calls XJCFacade which is the entry point of the XJC JAR
    tasks.create(name: taskName, type: JavaExec) {

        classpath configurations.jaxb
        main 'com.sun.tools.xjc.XJCFacade'

        // To explore available args, download the XJC JAR manually and run java -jar jaxb-xjc.jar --help
        args schema, "-p", pkg, "-d", dest
    }

    // Add a dependency on the new task so it gets invoked
    compileGeneratedJava.dependsOn tasks.getByName(taskName)
}

// Add all the XJC tasks you need

addXjcTask("xjcSchema1",
        "path/to/schema1.xsd",
        'com.example.generated.schema1',
        "$generated_dir")

addXjcTask("xjcSchema2",
        "path/to/schema2.xsd",
        'com.example.generated.schema2',
        "$generated_dir")
0

All of the other answers are outdated as the time of this writing.

  • Since Java EE was rebranded to Jakarta EE, JAXB is now provided by new artifact jakarta.xml.bind:jakarta.xml.bind-api.
  • JAXB runtime is given by org.glassfish.jaxb:jaxb-runtime.
  • XJC compiler is given by org.glassfish.jaxb:jaxb-xjc.

Jakarta XML Binding (previously called JAXB) Reference Implementation.

xjc options.

Putting it all together, a complete working example using Kotlin DSL:

val jaxb: Configuration by configurations.creating

val jaxbVersion: String by project    
val schemaDir = "src/main/resources"
val xjcOutputDir = "$buildDir/generated/source/xjc/main"

dependencies {
    jaxb("org.glassfish.jaxb:jaxb-xjc:$jaxbVersion")
    implementation("jakarta.xml.bind:jakarta.xml.bind-api:$jaxbVersion")
    runtimeOnly("org.glassfish.jaxb:jaxb-runtime:$jaxbVersion")
}

val createXjcOutputDir by tasks.register("createXjcOutputDir") {
    doLast {
        mkdir(xjcOutputDir)
    }
}

val xjc by tasks.registering(JavaExec::class) {
    // Directory needs to exist 
    dependsOn(createXjcOutputDir)
    classpath = jaxb
    mainClass.set("com.sun.tools.xjc.XJCFacade")
    args = listOf(
        "-d",
        xjcOutputDir,
        "-p",
        project.group.toString(),
        "-encoding",
        "UTF-8",
        "-no-header",
        "-quiet",
        schemaDir
    )
}

tasks.withType<JavaCompile>().configureEach {
    dependsOn(xjc)
}

sourceSets {
    main {
        java {
            srcDirs(
                files(xjcOutputDir) {
                    builtBy(xjc)
                }
            )
        }
    }
}
Abhijit Sarkar
  • 16,021
  • 13
  • 78
  • 152