Social networks
You can find me on:
   
Open sharing content

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

Archive for August, 2013

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());

If you want to specify that element values are CDATA, you can create an adapter class like this:

import javax.xml.bind.annotation.adapters.XmlAdapter;

/**
 * to parse CDATA values.
 * 
 * @author Laura Liparulo
 */
public class AdapterXmlCDATA extends XmlAdapter<String, String> {

    @Override
    public String marshal(String value) throws Exception {
        return "<![CDATA[" + value + "]]>";
    }
    @Override
    public String unmarshal(String value) throws Exception {
        return value;
    }

}

In the bean class, generated by JAXB add the following annotation.

    @XmlJavaTypeAdapter(AdapterXmlCDATA.class)
    protected String description;

In this way any occurrence of the elements with the annotation above will be automatically parsed as CDATA.