miércoles, 31 de agosto de 2011

"package javax.xml.bind.annotation does not exist" using CXF's DynamicClientFactory in OC4J 10.1.3.x

I'm using CXF 2.4.2 and DynamicClientFactory in OC4J 10.1.3.4 (and 10.1.3.5).

When DynamicClientFactory tries to compile the dinamycally generated Java sources, the following errors occur:

INFO: Created classes: XXX, YYY, ...
 C:\..\org.apache.cxf.endpoint.dynamic.DynamicClientFactory@12f767-1314800629516-src\...\XXX.java:4: package javax.xml.bind.annotation does not exist
 import javax.xml.bind.annotation.XmlAccessType;
                                  ^
 C:\..\org.apache.cxf.endpoint.dynamic.DynamicClientFactory@12f767-1314800629516-src\...\YYY.java:4: package javax.xml.bind.annotation does not exist
 import javax.xml.bind.annotation.XmlAccessType;
                                  ^
I dug into the DynamicClientFactory code and found that setupClasspath, the method which sets the Classpath for the javac compilation iterates between the supplied ClassLoader (or the Context ClassLoader if none is supplied) and the SystemClassLoader extrating the ClassPath paths for each one; this extraction is done only if the ClassLoader in turn is an instance of URLClassLoader. In OC4J, the ClassLoaders are usually instances of oracle.classloader.PolicyClassLoader (which is not an instance of URLClassLoader). For this reason, the Classpath for the compilation is not correctly set.

There was a reported bug (CXF-2549) which gives a solution for Weblogic. I didn't find a good similar solution for OC4J to send a patch.

I found the necessary compilation libraries are the JAXB ones. What I did, before calling createClient, was to set a new URLClassLoader which include the JAXB jars:

URL[] jaxbUrls = null;

jaxbUrls = new URL[]{
  new URL("file:/C:/.../jaxb-api-2.2.1.jar"),
  new URL("file:/C:/.../jaxb-impl-2.2.1.1.jar"),
  new URL("file:/C:/.../jaxb-xjc-2.2.1.1.jar")
};

ClassLoader currentCL = Thread.currentThread().getContextClassLoader();

URLClassLoader urlCL = new URLClassLoader(jaxbUrls, currentCL);

client = dcf.createClient(xxx, yyy, urlCL, zzz);

The compilation is now succesful.

DynamicClientFactory internally creates another URLClassLoader which includes the newly generated classes and set it as the current thread's Context Class Loader; for this reason, when working with DynamicClientFactory in a multithreaded environment, there are some gotchas relating the ClassLoaders, but I thing this is material for another post.

EDIT: I found a better way to initialize the JAXB jars URL's array:

import oracle.classloader.PolicyClassLoader;
import oracle.classloader.SharedCodeSource;

Class someJaxbClazz = Class.forName("javax.xml.bind.annotation.XmlAccessType");
PolicyClassLoader jaxbClazzClassLoader = (PolicyClassLoader)someJaxbClazz.getClassLoader();
SharedCodeSource[] sharedCodeSources = jaxbClazzClassLoader.getCodeSources(false);

for(SharedCodeSource sharedCodeSource : sharedCodeSources) {
    URL location = sharedCodeSource.getLocation();
    if(location.toString().toLowerCase().contains("jaxb")) {
        System.out.println("JAXB jar URL found->" + location);
    }
}

In my case, it printed somethig like:

JAXB jar URL found->file:/C:/.../cxf/2.4.2/jaxb-api-2.2.1.jar
JAXB jar URL found->file:/C:/.../cxf/2.4.2/jaxb-impl-2.2.1.1.jar
JAXB jar URL found->file:/C:/.../cxf/2.4.2/jaxb-xjc-2.2.1.1.jar

:-)





martes, 30 de agosto de 2011

"compiler was unable to honor this globalBindings customization" using CXF's DynamicClientFactory

I'm using CXF 2.4.2 and DynamicClientFactory. To simplify the runtime classes manipulation I wanted to make sure the generated Java clases didn´t contain JAXBElement attributes; so I created the famous generateElementProperty globalBindings JAXB binding file.

I used the createClient method that receives the binding list to feed DynamicClientFactory with my file, but I received the following JAXB error: compiler was unable to honor this globalBindings customization. It is attached to a wrong place, or its inconsistent with other bindings.

I found this error may occur if you use more than one binding file with globalBindings declarations. I dug into the DynamicClientFactory code and found that by default, CXF use one binding file with globalBindings declarations: /org/apache/cxf/endpoint/dynamic/simple-binding.xjb in the addSchemas method. Thus, besides my binding file, there was another file internally used by CXF. Luckily, the binding file included by CXF have the generateElementProperty declaration that I was trying to configure with my binding file.

I checked the runtime generated classes and indeed (modifying the DynamicClientFactory class to not delete the source files) they did'n include the JAXBElement attributes when they would normally do.

It was no necesary to include my extra binding file.

This is a fragment of the stack trace:

Caused by: java.lang.reflect.UndeclaredThrowableException
    at $Proxy25.error(Unknown Source)
    at com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.error(SchemaCompilerImpl.java:286)
    at com.sun.tools.xjc.util.ErrorReceiverFilter.error(ErrorReceiverFilter.java:77)
    at com.sun.tools.xjc.util.ErrorReceiverFilter.error(ErrorReceiverFilter.java:77)
    at com.sun.tools.xjc.ErrorReceiver.error(ErrorReceiver.java:82)
    at com.sun.tools.xjc.reader.xmlschema.ErrorReporter.error(ErrorReporter.java:79)
    at com.sun.tools.xjc.reader.xmlschema.UnusedCustomizationChecker.check(UnusedCustomizationChecker.java:144)
    at com.sun.tools.xjc.reader.xmlschema.UnusedCustomizationChecker.check(UnusedCustomizationChecker.java:122)
    at com.sun.tools.xjc.reader.xmlschema.UnusedCustomizationChecker.schema(UnusedCustomizationChecker.java:199)
    at com.sun.tools.xjc.reader.xmlschema.UnusedCustomizationChecker.run(UnusedCustomizationChecker.java:95)
    at com.sun.tools.xjc.reader.xmlschema.BGMBuilder._build(BGMBuilder.java:187)
    at com.sun.tools.xjc.reader.xmlschema.BGMBuilder.build(BGMBuilder.java:116)
    at com.sun.tools.xjc.ModelLoader.annotateXMLSchema(ModelLoader.java:415)
    at com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.bind(SchemaCompilerImpl.java:245)
    ... 32 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:592)
    at org.apache.cxf.common.util.ReflectionInvokationHandler.invoke(ReflectionInvokationHandler.java:52)
    ... 46 more
Caused by: java.lang.RuntimeException: 
    Error compiling schema from WSDL at {http://xxx?wsdl}: compiler was unable to honor this globalBindings customization. It is attached to a wrong place, or its inconsistent with other bindings.
    at org.apache.cxf.endpoint.dynamic.DynamicClientFactory$InnerErrorListener.error(DynamicClientFactory.java:582)
    ... 51 more
Caused by: com.sun.istack.SAXParseException2: compiler was unable to honor this globalBindings customization. It is attached to a wrong place, or its inconsistent with other bindings.
    ... 42 more


Update (2013/08/14): I tried the same codebase with CXF 2.6.1, but started having issues with JAXBElement attributes appearing in the generated Java classes. I dug again into the DynamicClientFactory code and found in SVN revision 1232564 ([CXF-4037] Allow dynamic client to use already parsed and processed schemas like the tooling does), the code section that included the default binding file (simple-binding.xjb) was removed. It haven't been included in posterior commits (last trunk revision now for DynamicClientFactory is 1502354).

This is one version of the DynamicClientFactory.java's removed code section:

if (simpleBindingEnabled) {
String id = "/org/apache/cxf/endpoint/dynamic/simple-binding.xjb";
LOG.fine("Loading the JAXB 2.1 simple binding for client.");
try {
Document doc = StaxUtils.read(getClass().getResourceAsStream(id));
compiler.parseSchema(id, doc.getDocumentElement());
} catch (XMLStreamException e) {
LOG.log(Level.WARNING, "Could not parse simple-binding.xsd", e);
}
}

Due to the fact my code is not expecting JAXBElement attributes in the generated classes, I'm including the binding (again) by myself.

My binding:

<bindings version="2.1"
  xmlns="http://java.sun.com/xml/ns/jaxb"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc">
  <bindings>
    <globalBindings>
      <xjc:generateElementProperty>false</xjc:generateElementProperty>
    </globalBindings>
  </bindings>
</bindings>

The CXF's equivalent binding:

<jaxb:bindings
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc">
 
  <jaxb:globalBindings generateElementProperty="false">
    <xjc:simple />
  </jaxb:globalBindings>
</jaxb:bindings>