Ant XJCTask works until JAXB extensions are added, which produces "Provider xx not a subtype" error

For quite a while I’ve been looking at converting a Maven build of a CXF JAX-RS application to use Gradle.

The gnarly part is using XJC (with JAXB extensions) to generate Java classes.

I’ve followed some advice to use the Ant XJCTask. This comes close, but it doesn’t work with JAXB extensions. It generates code if I don’t reference the extensions, but once I add them I get an error like the following:

java.util.ServiceConfigurationError: com.sun.tools.xjc.Plugin: Provider dk.conspicio.jaxb.plugins.XmlElementWrapperPlugin not a subtype

I get this error for both of the two JAXB extensions I’m using (I have to remove the reference to this one to see the similar error for the second one).

I’ve posted about this in various places, and the closest I’ve come is a note from Martin Gainty explaining how the ServiceLoader works. Unfortunately, that doesn’t really help me see a solution.

Note that I tried the recently added JAXB Gradle plugin. Unfortunately, at this time that plugin is too restrictive, not allowing me to specify individual XSD files (it just takes a directory name, and it processes all XSDs in the folder). I’ve proposed an extension to the author, and I’ve looked at the code to see what might be required to do this, but in the meantime I’m attempting to get this working with just the Ant task.

The following is the task I’ve defined, along with the dependencies I added for this.

task processXSDs() << {
 ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask',
    classpath: configurations.jaxb.asPath)
   ant.xjc(destdir: 'tmp', package: "com.att.sunlight.service.domain.serviceCallResults", extension: true) {
  schema(dir: "src/main/resources/schema", includes: "serviceCallResults.xsd")
        arg(value: "-Xxew")
        arg(value: "-summary target/xew-summary.txt")
        arg(value: "-instantiate lazy")
        arg(value: "-Xfluent-api")
 }
  }
   jaxb 'com.sun.xml.bind:jaxb-xjc:2.2.6'
 jaxb 'com.sun.xml.bind:jaxb-impl:2.2.6'
 jaxb 'javax.xml.bind:jaxb-api:2.2.6'
 jaxb "JAXBXMLElementWrapperPlugin:JAXBXMLElementWrapperPlugin:1.0.0"
 jaxb "net.java.dev.jaxb2-commons:jaxb-fluent-api:2.1.8"

I had one suggestion to add a classpath setting to the xjc element also. That makes no difference. I had originally set the “line” property of the “arg” elements instead of the “value” property, but neither makes any difference to the problem.

Any ideas?

1 Like

I’m not entirely sure how the ServiceLoader works but perhaps this can be fixed in a similar way to the JDBC hack here

eg:

configurations {
    jaxb
}
dependencies {
    jaxb 'com.sun.xml.bind:jaxb-xjc:2.2.6'
    jaxb 'com.sun.xml.bind:jaxb-impl:2.2.6'
    jaxb 'javax.xml.bind:jaxb-api:2.2.6'
    jaxb "JAXBXMLElementWrapperPlugin:JAXBXMLElementWrapperPlugin:1.0.0"
    jaxb "net.java.dev.jaxb2-commons:jaxb-fluent-api:2.1.8"
}
  task processXSDs() << {
    URLClassLoader loader = GroovyObject.class.classLoader
    configurations.jaxb.each { File file ->
        loader.addURL(file.toURI().toURL())
    }
        ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask')
    ant.xjc(destdir: 'tmp', package: "com.att.sunlight.service.domain.serviceCallResults", extension: true)
    ...
    }

Dang. That looked promising. I still get the same error.

Perhaps you could do this with javaexec

task processXSDs(type: JavaExec) {
    classpath configurations.jaxb
    main = "com.sun.tools.xjc.XJCFacade"
    // see https://jaxb.java.net/nonav/2.0.2/docs/xjc.html
    args ["-Xxew", "-summary", file("target/xew-summary.txt").absolutePath, "-instantiate", "lazy", "-Xfluent-api", ...]
}

Where can I find the javadoc for “com.sun.tools.xjc.XJCFacade” so I can determine what all the expected command-line arguments are? I’m going to have to pass several parameters that are currently being set from XJCTask properties.

I’m going to have to call this task several times, because each of my XSDs requires a different package name (I think it’s possible to do this with a single bindings file mapping to each namespace, but I’d rather be explicit about this in the build file, as opposed to in a separate file).

How can I maximize code reuse here?

Where can I find the javadoc for “com.sun.tools.xjc.XJCFacade” so I can determine what all the expected command-line arguments are?

This should help http://docs.oracle.com/javase/8/docs/technotes/tools/unix/xjc.html

How can I maximize code reuse here?

You could package the task as a plugin with a custom plugin extension http://www.gradle.org/docs/current/userguide/custom_plugins.html#N16B32

Here’s my first attempt at the new task:

task processServiceCallResultsXSD(type: JavaExec) {
 classpath configurations.jaxb
 main = "com.sun.tools.xjc.XJCFacade"
 args = ["-classpath", configurations.jaxb, "-p", "com.att.sunlight.service.domain.serviceCallResults",
         "-d", "tmp", "-extension",
      "-Xxew", "-summary", file("target/xew-summary.txt").absolutePath, "-instantiate", "lazy", "-Xfluent-api",
      "src/main/resources/schema/serviceCallResults.xsd"]
}
compileJava.dependsOn processServiceCallResultsXSD

Here’s my result (no change):

:processServiceCallResultsXSDException in thread "main" java.util.ServiceConfigurationError: com.sun.tools.xjc.Plugin: Provider dk.conspicio.jaxb.plugins.XmlElementWrapperPlugin not a subtype
 at java.util.ServiceLoader.fail(ServiceLoader.java:214)
 at java.util.ServiceLoader.access$300(ServiceLoader.java:164)
 at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:352)
 at java.util.ServiceLoader$1.next(ServiceLoader.java:428)
 at com.sun.tools.xjc.Options.findServices(Options.java:965)
 at com.sun.tools.xjc.Options.getAllPlugins(Options.java:383)
 at com.sun.tools.xjc.Options.parseArgument(Options.java:705)
 at com.sun.tools.xjc.Driver$OptionsEx.parseArgument(Driver.java:500)
 at com.sun.tools.xjc.Options.parseArguments(Options.java:815)
 at com.sun.tools.xjc.Driver.run(Driver.java:239)
 at com.sun.tools.xjc.Driver.run(Driver.java:199)
 at com.sun.tools.xjc.Driver._main(Driver.java:122)
 at com.sun.tools.xjc.Driver.access$000(Driver.java:79)
 at com.sun.tools.xjc.Driver$1.run(Driver.java:102)
 FAILED

I tried both with and without the “-classpath” parameter. No change.

1 Like

Perhaps it’s easier to take Gradle out of the picture for the moment and get xjc working from the command line?. Once that’s working it will be simple to convert to a Gradle task / plugin.

Also, the following won’t work

args
  = ["-classpath", configurations.jaxb, ... ]

All args must be strings and configurations.jaxb is an instance of ConfigurationContainer

Yeah, it’s simple to produce that command line, but it doesn’t solve the root problem. That simple command line still fails with the exact same error I’ve been battling with. I didn’t even bother to specify my extension args or a schema, I just ran XJCFacade with the jars from my classpath (I added println statements to that code to add them to the URLClassLoader so I would know exactly what jars are being used), and that fails with the consistent “Provider xx not a subtype”.

You know what Martin Gainty finally recommended to me, when I asked him to clarify why this works in Maven and not elsewhere? He said “Just use Maven”. I really don’t want to accept that conclusion.

Just curious, have you tried it with just Ant? Would at least help identify if it is a weird classloader issue.

I just put together an Ant build script to test this. It gets the same error. I used the same jars in my Gradle cache.

If it matters, here’s an excerpt from what Martin Gainty said about this: “Maven solves the issue by inheriting System CL at CLI before tacking on maven-core and any consequent plugins http://maven.apache.org/guides/mini/guide-maven-classloading.html

I’m not sure how to interpret that.

@mark’s suggestion of removing gradle by trying ant only was a good one. Better than my suggestion of trying xjc from command line.

I’d say you’re best to take your ant only question to stack overflow now that gradle has been eliminated. I’m guessing maven is bootstrapping a library on the classpath which you’re not including. Oh, and by the way in Martin’s response CL=classloader and CLI=Command Line Interface

I guess I have more information now. I already posted this on SO, ant-user, cxf-user, and jaxb-user, but I guess I’ll try again on SO.

The CL and CLI in Martin’s response was clear. His implication is not that Maven is loading a different library, it’s that Maven is loading them in a different way than the “obvious” way.