Search code examples
javaapache-cameljaxbintegration

How to generate JAXB data format from XSD schema?


When I try to marshal data I got from the database in Apache Camel using JAXB and providing XSD schema I get the error

java.io.org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: java.util.LinkedHashMap to the required type: java.io.InputStream with value {id=5, number=5599, type=B3, ... }

when I try to send the message to ActiveMQ. I'm new to integration and this is my intern Camel project. When I marshal the message to json everything is alright. I thought about converting the message to json and then to XML, but it seems to me that isn't the way I should do it. I've got a prepared XSD schema that looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
       xmlns:pc="com.release11.packages"
       targetNamespace="com.release11.materials">
<xs:import schemaLocation="packages.xsd"
           namespace="com.release11.packages"/>

<xs:simpleType name="materialTypeType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="A1"/>
        <xs:enumeration value="A2"/>
        <xs:enumeration value="A3"/>
        <xs:enumeration value="B1"/>
        <xs:enumeration value="B2"/>
        <xs:enumeration value="B3"/>
        <xs:enumeration value="C1"/>
        <xs:enumeration value="C2"/>
        <xs:enumeration value="C3"/>
    </xs:restriction>
</xs:simpleType>

<xs:complexType name="materialType">
    <xs:sequence>
        <xs:element name="Id" type="xs:integer"/>
        <xs:element name="Number" type="xs:integer"/>
        <xs:element name="Type" type="materialTypeType"/>
        <xs:element name="Name" type="xs:string"/>
        <xs:element name="Description" type="xs:string"/>
        <xs:element name="Is_deleted" type="xs:boolean"/>
        <xs:element ref="pc:Packages" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="materialsType">
    <xs:sequence>
        <xs:element name="Material" type="materialType" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

<xs:element name="Materials" type="materialsType"/>

</xs:schema>

I tried to find the answer on the web, but I found nothing useful or I couldn't understand the answer so I need someone to explain this to me. Please help me.

Here's my code:

public class InputAdapter {

public static void main(String[] args) throws Exception {

    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/Packages");
    dataSource.setUsername("uname");
    dataSource.setPassword("passwd");
    SimpleRegistry registry = new SimpleRegistry();
    registry.bind("dataSource", dataSource);

    ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
    activeMQConnectionFactory.setBrokerURL("tcp://127.0.0.1:61616");

    Connection connection = activeMQConnectionFactory.createConnection();
    connection.start();

    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    Destination destination = session.createQueue("MESSAGES_RAW");

    MessageProducer producer = session.createProducer(destination);
    producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

    CamelContext context = new DefaultCamelContext(registry);
    context.addComponent("activemq", JmsComponent.jmsComponentAutoAcknowledge(activeMQConnectionFactory));

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() {
            JaxbDataFormat dataFormat = new JaxbDataFormat();
            dataFormat.setSchemaLocation("material.xsd");

            from("timer://foo?repeatCount=1")
                    .setBody(constant("SELECT * FROM material;"))
                    .to("jdbc:dataSource")
                    .split(body())
                    .marshal(dataFormat)
                    .to("activemq:queue:MESSAGES_RAW");
        }
    });
    context.start();
    Thread.sleep(1000);
    context.stop();
}
}

Solution

  • Generating classes from XSD with CLI tool

    You can use xjc command-line tool that comes pre-installed with at JDK 8 to generate jaxb classes.

    Example:

    xjc material.xsd
    
    # With groupId
    xjc -p <groupId> material.xsd
    
    # With groupId and bindings configuration file
    xjc -p <groupId> -b bindings.xjb material.xsd
    

    Generating classes from XSD using maven plugin

    Alternatively you can use maven plugin to do the same. By default the plugin will look for schema xsd files from src/main/xsd and bindings xjb files from src/main/xjb

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>2.5.0</version>
                <executions>
                    <execution>
                        <id>xjc</id>
                        <goals>
                            <goal>xjc</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <packageName>com.example.group</packageName>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    If you're using JDK 11 or later you'll also have to include couple of related dependencies that are no longer included in the JDK.

    <dependencies>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-xjc</artifactId>
            <version>2.3.6</version>
        </dependency>
    </dependencies>
    

    With these maven should generate the classes to target/generated-sources/jaxb folder after running mvn clean install. With maven plugin you're better off creating separate api-project for these and adding it as a dependency for your camel integration project.

    Usage in camel

    You can use jaxb with camel by creating JaxbDataFormat using JAXBContext instance.

    JAXBContext jaxbContext = JAXBContext.newInstance(Materials.class);
    JaxbDataFormat jaxbDataformat = new JaxbDataFormat(jaxbContext);
    
    from("direct:marshalMaterials")
        .routeId("marshalMaterials")
        .marshal(jaxbDataformat)
        .log("${body}");
    

    Since you're querying a database that by default returns list of maps, you'll have to convert it to appropriate Jaxb object. You can use generated ObjectFactory class to generate different jaxb class instances.

    With JDK 11 you might also need following dependencies

    <dependency>
        <groupId>jakarta.xml.bind</groupId>
        <artifactId>jakarta.xml.bind-api</artifactId>
        <version>2.3.3</version>
    </dependency>
    
    <!-- versions obtained from dependency-management camel-bom -->
    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
    </dependency>
    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-core</artifactId>
    </dependency>
    

    Namespace change from javax to jakarta

    More recent versions of jaxb are using jakarta namespace instead of javax. If you want to use the newer jakarta namespace instead you can use jaxb2-maven-plugin and jakarta.xml.bind-api version 3.x or higher. Support for Jakarta namespace is planned for Camel 4.x so if you're using Camel 3.x you might want to wait or upgrade to that first.

    Bindings XJB files

    With bindings xjb files you can tweak how classes get generated like change class or property names, prevent nested class mess etc.

    Example: Empty binding file template

    <?xml version="1.0" encoding="UTF-8"?>
    <jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
        xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
        xmlns:xs="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        version="2.1" 
        xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">
    
        ...
    
    </jaxb:bindings>