Social networks
You can find me on:
   
Open sharing content

These articles are available under Creative Commons license BY-SA-3.0

Posts Tagged ‘jaxb’

After using JAXB with XSD schema, I have finally discovered that it´s MUCH better to make it with files and jaxd binding files.

Supposing you want to generate the classes for a web-app xml file, in the Maven pom.xml you need to add something like:

<plugin>
  <groupId>org.jvnet.jaxb2.maven2</groupId>
   <artifactId>maven-jaxb2-plugin</artifactId>
      <executions>
 	<execution>
        	<goals>
	        	<goal>generate</goal>
		</goals>
		<configuration>
		<!-- if you want to put DTD somewhere else <schemaDirectory>src/main/jaxb</schemaDirectory> -->
			<schemaDirectory>src/main/resources/web</schemaDirectory>
			   <generateDirectory>${basedir}/src/main/java</generateDirectory>
				<packagename>com.my.package</packagename>
		 		    <extension>true</extension>
					<schemaLanguage>DTD</schemaLanguage>
					    <schemaIncludes>
						<schemaInclude>*.dtd</schemaInclude>
					    </schemaIncludes>
					<bindingIncludes>
					    <bindingInclude>*.jaxb</bindingInclude>
						</bindingIncludes>
						<args>
						   <arg>-Xinject-listener-code</arg>
						</args>
						</configuration>
					</execution>
				</executions>
		<dependencies>
			<dependency>
				<groupId>org.jvnet.jaxb2-commons</groupId>
				<artifactId>property-listener-injector</artifactId>
				<version>1.0</version>
			</dependency>
		</dependencies>
</plugin>

In the specified schema directory there must be both the .dtd and the .jaxb binding files.
To specify a package for the generated classes, in the jaxb file like:

<?xml version="1.0" ?>
     <xml-java-binding-schema xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  xmlns:ci="http://jaxb.dev.java.net/plugin/listener-injector">
         <options package="deploy.service.webapp" />
            <xjc:serializable/>
    </xml-java-binding-schema>

You can find a full demo of a web-app xml schema classes generation at sourceforge: JAXB DTD maven demo

Currently I am developing an application to rename some jboss configuration content.
Using JAXB, I had made marshalling and unmarshalling methods, creating a new JAXBcontext instance each time the single methods were invoked (for each xml file!).
The application was incredibly slow. Taking up to 45 minutes to complete the tasks.
Then I have discovered that I could make it all in less than 30% of the time, by using a singleton design pattern to reuse a single instance of the JAXBContext.

All you need to do is:
1) creating a singleton class to instance the context
2) use it in your marshalling/unmarshalling method.

The singleton class is like this:

import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import com.nexus.pacs.conf.xmdesc.Mbean;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public class MbeanJAXBContext {

	private static JAXBContext instance;

	public XmdescJAXBContext() {
	}

	public static synchronized JAXBContext initContext() {
		try {
			if (instance == null)
				instance = JAXBContext.newInstance(Mbean.class);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return instance;
	}

}

I am using the synchronized keyword to make it thread safe.
Then you can instance your context like this:

public static void marshaller(Mbean mbean, String filePath)
			throws CustomizerException {

	StringWriter stringWriter = new StringWriter();
	String xmlContent;
	File file = new File(filePath);

	try {
		xmlContent = FileUtils.readFileToString(file, "UTF-8");
		FileWriter fileWriter = new FileWriter(file);
		XmdescJAXBContext jaxbHelper = new XmdescJAXBContext();
		JaxbContext jaxbContext= jaxbHelper.initContext();
		Marshaller marshaller = jaxbContext.createMarshaller();

                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

		marshaller.setProperty(CharacterEscapeHandler.class.getName(),
					new XmlCharacterHandler());

		 synchronized(marshaller){
		     marshaller.marshal(mbean, stringWriter);
		    }
		
	xmlContent = XmdescContentHandler.addEntityReferences(xmlContent,
					stringWriter.toString());
		fileWriter.write(xmlContent);

		fileWriter.flush();
		fileWriter.close();

		} catch (MarshalException e) {

		throw new CustomizerException("Marshalling the file: " + filePath
					+ "not possible. Wrong file content");

		} catch (JAXBException e) {

		throw new CustomizerException("Could not parse the XML file: "
					+ filePath);

		} catch (IOException e) {

		throw new CustomizerException("Could not access the file: "
					+ filePath);
		}

	}

Awfully I have also noticed that with Ubuntu 12.04, with the singleton instance, the application now takes just a minute to run, while with Windows 7 it still takes 16 minutes. Long live Linux!

You can find a post about the Character Handler in this blog. Have fun!

Let´s consider an xml file with entity references.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mbean PUBLIC "members of the whole firm" "team.dtd">
<team category="developer">
    <Member role="junior">Laura</Member>
    <Member role="senior">Mike</Member>
    &john;
</team>

In your dtd schema you can specify the reference.

<?xml version="1.0" encoding="UTF-8"?>
  <!ENTITY john '<Member role="boss">John</Member>'>

If you need to unmarshal an xml file with entity references, first of all you need to expand them.

public static String xmlStringEntityResolver(String filePath)
			throws ParserConfigurationException, SAXException, IOException,
			TransformerException, JAXBException {

		File file = new File(filePath);
		DocumentBuilderFactory builderFactory = DocumentBuilderFactory
				.newInstance();
		// Specifies that the parser produced by this code will
		// expand entity reference nodes
		builderFactory.setExpandEntityReferences(true);
		DocumentBuilder builder = builderFactory.newDocumentBuilder();
		Document doc = builder.parse(file);
		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");

		StringWriter writer = new StringWriter();
		StreamResult result = new StreamResult(writer);
		DOMSource source = new DOMSource(doc);
		transformer.transform(source, result);
		// System.out.println("Expandend xml file: \n" + writer.toString());
		return writer.toString();
	}

Then you can unmarshal the file passing it as a string file.

public static Team unmarshall(String xmlString)
			throws ParserConfigurationException, SAXException, IOException,
			TransformerException, JAXBException {

		// unmarshaller from string
		JAXBContext jaxbContext = JAXBContext.newInstance(Team.class);
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		InputStream stream = new ByteArrayInputStream(
				xmlString.getBytes("UTF-8"));
		Team team = (Team) unmarshaller.unmarshal(stream);

		return team;

	}

I would like to find a better way. I´ll see what I can do.

With JAXB you need to dhandle special characters you can just choose between two options.

Otherwise you need to define your own character handler.

The following class shows you how to handle both CDATA elements and special characters.

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

public class XmlCharacterHandler implements CharacterEscapeHandler {

	public void escape(char[] buf, int start, int len, boolean isAttValue,
			Writer out) throws IOException {
		StringWriter buffer = new StringWriter();

		for (int i = start; i < start + len; i++) {
			buffer.write(buf[i]);
		}

		String st = buffer.toString();

		if (!st.contains("CDATA")) {
			st = buffer.toString().replace("&", "&amp;").replace("<", "&lt;")
					.replace(">", "&gt;").replace("'", "&apos;")
					.replace("\"", "&quot;");

		}
		out.write(st);
//		System.out.println(st);
	}

}

In your marshaller method just add the following property:

	marshaller.setProperty(CharacterEscapeHandler.class.getName(),
					new XmlCharacterHandler());

Although many XML files have a DOCTYPE declaration, browsing the internet and my safary library account haven´t helped that much to unmarshall (and marshall) my mbeans descriptors with JAXB. It tooks me several hours to find a way out.
Supposing that the xml root element is called “Bean”, you can get the header, simply converting the xml file in a string and taking all the content until the first root element tag (). This might be very useful if you want to scan a folder in which the xml files have different headers (like jboss services configurationes ones).

	public static String getHeader(String filePath) {

		String header = "";
		String xmlContent = "";
		int headerLastIndex = 0;

		File file = new File(filePath);

		String text = "";
		try {
			text = FileUtils.readFileToString(file, "UTF-8");

			headerLastIndex = text.indexOf("<mbean>");
			header = text.substring(0, headerLastIndex);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return header;

	}
	public static void marshaller(Mbean mbean, String filePath, String header)
			throws CustomizerException {

		stringWriter = new StringWriter();

		File file = new File(filePath);
		try {

			jaxbContext = JAXBContext.newInstance(Mbean.class);

			fileWriter = new FileWriter(file);

			marshaller = jaxbContext.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

			marshaller.marshal(mbean, stringWriter);

			System.out.println(stringWriter.toString());

			fileWriter.write(header+stringWriter.toString());

			fileWriter.flush();
			fileWriter.close();

		} catch (MarshalException ex) {

			throw new CustomizerException("Marshalling the file: " + filePath
					+ "not possible. Wrong file content");

		} catch (JAXBException e) {

			throw new CustomizerException("Could not parse the XML file: "
					+ filePath);

		} catch (IOException e) {

			throw new CustomizerException("Couldn´t access the file: "
					+ filePath);
		}

		finally {

			try {
				fileWriter.close();
			} catch (IOException e) {

				throw new CustomizerException(
						"Couldn´t close the fileWriter for: " + filePath);
			}
		}

	}

I would like to make it in a better way with JAXB, but I think that the second version is still a bit raw. Currently there are no unmarshalling properties provided. You can invoke the method setProperty(java.lang.String name, java.lang.Object value), but there are still no properties provided by the API. I would like to contribute to the project…

If you want to geenrate JAXB classes from a xsd schema in a Maven project, you need to specify the dependencies for jaxb and the plugin.

The pom.xml file is like the following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>de.demo.maven.jaxb</groupId>
	<artifactId>MAVEN_JAXB_DEMO</artifactId>
	<version>0.0.1-SNAPSHOT</version>

<dependencies>
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.2.5</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.2.5</version>
</dependency>
</dependencies>
	
 <build>
    <plugins>
        <!-- JAXB xjc plugin that invokes the xjc compiler to compile XML schema into Java classes.-->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxb2-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>xjc</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- The schema directory or xsd files. -->
                <schemaDirectory>${basedir}/src/main/resources</schemaDirectory>
                <!-- The package in which the source files will be generated. -->
                <packageName>de.demo.team</packageName>
                <!-- The working directory to create the generated java source files. -->
                <outputDirectory>${basedir}/src/main/java</outputDirectory>
            </configuration>
        </plugin>
    </plugins>
  </build>
	
</project>

in the configuration tags you need to specify “schemaDirectory” (the location of the .xsd file) and in the “outputDirectory” the destination folder for the generated classes.

First of all you need to run the maven compile command. Then you can generate the JAXB classes: in eclipse, for example, you can right-click on the project and run New>Other>JAXB>”JAXB Classes from a Schema”.
Of course you need to add the Eclipse plugin first.

Eclipse won´t generate automatically the @XmlRootElement annotation. You will have to add it on your own, like this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MBean", propOrder = {
    "attribute"
})
@XmlRootElement
public class MBean {
...
}