All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nz.net.ultraq.jaxb.XMLWriter.groovy Maven / Gradle / Ivy

/*
 * Copyright 2012, Emanuel Rabina (http://www.ultraq.net.nz/)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nz.net.ultraq.jaxb

import org.xml.sax.SAXException

import com.sun.xml.bind.marshaller.NamespacePrefixMapper

import javax.xml.bind.JAXBContext
import javax.xml.bind.JAXBException
import javax.xml.bind.Marshaller
import javax.xml.bind.PropertyException
import javax.xml.bind.ValidationEvent
import javax.xml.bind.util.ValidationEventCollector

/**
 * XML file marshaller using JAXB to turn JAXB-annotated classes into XML files.
 * 
 * @param  A JAXB generated class representing the root element of the XML
 * 			  document to be written.
 * @author Emanuel Rabina
 */
class XMLWriter extends XMLValidatingProcessor {

	private static final String CHARACTER_ESCAPE_HANDLER = 'com.sun.xml.bind.marshaller.CharacterEscapeHandler'
	private static final String NAMESPACE_PREFIX_MAPPER  = 'com.sun.xml.bind.namespacePrefixMapper'

	private final Marshaller marshaller

	/**
	 * Sets-up the XML marshaller.
	 *
	 * @param xmlPack Name of the package containing JAXB-generated classes.
	 * @throws XMLException
	 */
	XMLWriter(String xmlPack) throws XMLException {

		try {
			def jaxbcontext = JAXBContext.newInstance(xmlPack)
			marshaller = jaxbcontext.createMarshaller()
		}
		catch (JAXBException ex) {
			throw new XMLException('An error occurred when creating the marshaller', ex)
		}
	}

	/**
	 * Sets-up the XML marshaller.
	 * 
	 * @param xmlClasses List of classes that the reader needs to recognize.
	 * @throws XMLException
	 */
	XMLWriter(Class... xmlClasses) throws XMLException {

		try {
			def jaxbcontext = JAXBContext.newInstance(xmlClasses)
			marshaller = jaxbcontext.createMarshaller()
		}
		catch (JAXBException ex) {
			throw new XMLException('An error occurred when creating the marshaller', ex)
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	void clearValidatingSchemasImpl() throws JAXBException {

		marshaller.schema       = null
		marshaller.eventHandler = null
	}

	/**
	 * Set whether the resulting XML file will be formatted 'nicely'.
	 * 
	 * @param formatOutput
	 * @throws XMLException If there was some issue in accepting the 'format'
	 * 		   property.
	 */
	void setFormatOutput(boolean formatOutput) {

		try {
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formatOutput)
		}
		catch (PropertyException ex) {
			throw new XMLException("Unable to set 'format output' property to $formatOutput", ex)
		}
	}

	/**
	 * Sets a NamespacePrefixMapper implementation to allow control
	 * over the namespace prefixes used in the marshaller.
	 * 
	 * @param prefixMapper NamespacePrefixMapper implementation.
	 * @throws XMLException
	 */
	void setNamespacePrefixMapper(NamespacePrefixMapper prefixMapper) throws XMLException {

		try {
			marshaller.setProperty(NAMESPACE_PREFIX_MAPPER, prefixMapper)
		}
		catch (PropertyException ex) {
			throw new XMLException('Unable to specify a custom namespace prefix mapping', ex)
		}
	}

	/**
	 * Sets-up a schema location to go into the output XML.  If this method is
	 * called more than once, then only the latest schema location is used.
	 * 
	 * @param namespace Schema target namespace.
	 * @param url		Schema URL.
	 * @throws XMLException
	 */
	void setSchemaLocation(String namespace, String url) throws XMLException {

		try {
			marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "$namespace $url")
		}
		catch (PropertyException ex) {
			throw new XMLException('Unable to specify a schema location', ex)
		}
	}

	/**
	 * Enable/Disable CDATA sections in the XML output, provided the XML objects
	 * being serialized are annotated to make use of the @{link XMLCDataAdapter}.
	 * 
	 * @param useCDATASections
	 * @throws XMLException
	 */
	void setUseCDATASections(boolean useCDATASections) throws XMLException {

		try {
			marshaller.setProperty(CHARACTER_ESCAPE_HANDLER, useCDATASections ?
					new XMLCDataEscapeHandler() : null)
		}
		catch (PropertyException ex) {
			throw new XMLException(
					"Unable to ${useCDATASections ? 'enable' : 'disable'} the use of CDATA sections", ex)
		}
	}

	/**
	 * Marshalls the given XML to the given file.
	 * 
	 * @param xmlroot Object representing the root element of the resulting XML
	 * 				  document.
	 * @param output  File to write the output to.
	 * @throws XMLException If an error occurred during marshalling, or at least
	 * 		   one schema was set against the marshaller and the output didn't
	 * 		   conform to the schema.
	 */
	void writeXMLData(T xmlroot, File output) throws XMLException {

		try {
			writeXMLData(xmlroot, new BufferedWriter(new FileWriter(output)))
		}
		catch (IOException ex) {
			throw new XMLException('Output file cannot be written', ex)
		}
	}

	/**
	 * Marshalls the given XML to the given output stream.
	 * 
	 * @param xmlroot Object representing the root element of the resulting XML
	 * 				  document.
	 * @param output  Stream to write the output to.
	 * @throws XMLException If an error occurred during marshalling, or at least
	 * 		   one schema was set against the marshaller and the output didn't
	 * 		   conform to the schema.
	 */
	void writeXMLData(T xmlroot, OutputStream output) throws XMLException {

		writeXMLData(xmlroot, new OutputStreamWriter(output))
	}

	/**
	 * Marshalls the given XML to the given writer.
	 * 
	 * @param xmlroot Object representing the root element of the resulting XML
	 * 				  document.
	 * @param output  File to write the output to.
	 * @throws XMLException If an error occurred during marshalling, or at least
	 * 		   one schema was set against the marshaller and the output didn't
	 * 		   conform to the schema.
	 */
	void writeXMLData(T xmlroot, Writer output) throws XMLException {

		try {
			// Combine and apply schemas to the marshaller
			if (!schemaBuilt && !sources) {
				marshaller.schema       = buildValidatingSchema()
				marshaller.eventHandler = new ValidationEventCollector()
				schemaBuilt = true
			}

			// Marshall
			marshaller.marshal(xmlroot, output)

			// Check for validation errors (if validating)
			if (marshaller.schema) {
				def vec = (ValidationEventCollector)marshaller.eventHandler
				if (vec.hasEvents()) {
					def eventList = new StringBuilder()
					vec.events.each { event -> eventList << "${event.message}\n" }
					throw new XMLException(
							"Validation errors were detected in the output: ${eventList.toString().trim()}")
				}
			}
		}
		catch (SAXException ex) {
			throw new XMLException('An error occurred when processing the validating schemas', ex)
		}
		catch (JAXBException ex) {
			throw new XMLException('An error occurred during marshalling', ex)
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy