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

com.cedarsolutions.util.JaxbUtils Maven / Gradle / Ivy

There is a newer version: 5.8.4
Show newest version
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2013-2014 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici 
 * Language : Java 6
 * Project  : Common Java Functionality
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.util;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.util.ValidationEventCollector;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

import com.cedarsolutions.exception.CedarRuntimeException;
import com.cedarsolutions.exception.InvalidDataException;
import com.cedarsolutions.shared.domain.ValidationErrors;

/**
 * JAXB utilities that operate on a cached JAXB context.
 *
 * 

* The process of establishing a JAXB context can be pretty slow. So, we want * to avoid having to do it more often than necessary. This class keeps an * internal cache of contexts that have already been established. It also * provides some utility methods to simplify marshalling and unmarshalling. *

* * @author Kenneth J. Pronovici */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class JaxbUtils { /** Validation error key used for XML errors. */ public static final String ERROR_KEY = "jaxb"; /** Singleton instance. */ private static JaxbUtils INSTANCE; /** Map from class name to generated JAXB context. */ private Map contextMap = new HashMap(); /** Default constructor is private so class cannot be instantiated. */ private JaxbUtils() { } /** Get an instance of this class to use. */ public static synchronized JaxbUtils getInstance() { if (INSTANCE == null) { INSTANCE = new JaxbUtils(); } return INSTANCE; } /** * Get a JAXB context for the indicated class. * @param className Name of the class to get a context for * @return JAXB context for the class. */ public JAXBContext getJaxbContext(String className) { try { Class clazz = Class.forName(className); return this.getJaxbContext(clazz); } catch (Exception e) { throw new CedarRuntimeException("Error obtaining JAXB context: " + e.getMessage(), e); } } /** * Get a JAXB context for the indicated class. * @param clazz Class to get a context for. * @return JAXB context for the class. */ public synchronized JAXBContext getJaxbContext(Class clazz) { if (!this.contextMap.containsKey(clazz.getName())) { try { JAXBContext context = JAXBContext.newInstance(clazz); this.contextMap.put(clazz.getName(), context); } catch (Exception e) { throw new CedarRuntimeException("Error obtaining JAXB context: " + e.getMessage(), e); } } return this.contextMap.get(clazz.getName()); } /** * Marshal an object of type T, creating XML with no schema location. * @param Type of the object * @param value Value to marshal. * @return Generated XML. */ public String marshalDocument(T value) { return marshalDocument(value, null); } /** * Marshal an object of type T, creating XML. * @param Type of the object * @param value Value to marshal. * @param schema Location of the schema to be placed in the XML, or null * @return Generated XML. */ public String marshalDocument(T value, String schema) { try { JAXBContext context = this.getJaxbContext(value.getClass()); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false); if (schema != null) { marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schema); } StringWriter writer = new StringWriter(); marshaller.marshal(value, writer); return writer.toString(); } catch (JAXBException e) { throw new CedarRuntimeException("Error marshalling XML: " + e.getMessage(), e); } } /** * Unmarshal XML, creating an object of type T. * @param Type of the object * @param type Type of the object to unmarshal * @param xml XML to use as source * @return Object of type T, unmarshalled from the XML. */ public T unmarshalDocument(Class type, String xml) { return unmarshalDocument(type, xml, true); } /** * Unmarshal XML, creating an object of type T. * @param Type of the object * @param type Type of the object to unmarshal * @param xml XML to use as source * @param validate Whether to validate the unmarshalling step * @return Object of type T, unmarshalled from the XML. * @throws InvalidDataException If the XML could not be parsed or if there are validation errors. */ public T unmarshalDocument(Class type, String xml, boolean validate) { try { JAXBContext context = this.getJaxbContext(type); StringReader reader = new StringReader(xml); Source source = new StreamSource(reader); if (!validate) { Unmarshaller unmarshaller = context.createUnmarshaller(); JAXBElement element = unmarshaller.unmarshal(source, type); return element.getValue(); } else { // If an adapter fails, we don't get an unmarshal exception. // Instead, we have to register and interrogate a handler. // See also: http://java.net/jira/browse/JAXB-537 Unmarshaller unmarshaller = context.createUnmarshaller(); String xsd = this.generateSchema(type); SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = sf.newSchema(new StreamSource(new StringReader(xsd))); ValidationEventCollector eventHandler = new ValidationEventCollector(); unmarshaller.setEventHandler(eventHandler); unmarshaller.setSchema(schema); JAXBElement element = unmarshaller.unmarshal(source, type); if (eventHandler.hasEvents()) { throw generateValidationError(type, eventHandler); } return element.getValue(); } } catch (JAXBException e) { throw translateJaxbUnmarshalError(type, e); } catch (SAXException e) { InvalidDataException invalid = getUnmarshalError(type, e); invalid.getDetails().addMessage(ERROR_KEY, e.getMessage()); throw invalid; } } /** * Generate the XML schema for a JAXB type. * @param Type of the object * @param type Type of the object to unmarshal */ public String generateSchema(Class type) { try { JAXBContext context = this.getJaxbContext(type); SchemaResolver resolver = new SchemaResolver(); context.generateSchema(resolver); return resolver.getWriter().toString(); } catch (IOException e) { throw new CedarRuntimeException("Error generating schema: " + e.getMessage(), e); } } /** Get a "raw" invalid data exception for a class, with empty details attached. */ private static InvalidDataException getUnmarshalError(Class type, Throwable cause) { ValidationErrors details = new ValidationErrors(ERROR_KEY, "Error unmarshalling XML for " + type.getSimpleName()); return new InvalidDataException("Error unmarshalling XML for " + type.getSimpleName(), details); } /** A resolver used for generating a schema from JAXB. */ private static class SchemaResolver extends SchemaOutputResolver { private StringWriter writer = new StringWriter(); public StringWriter getWriter() { return this.writer; } @Override public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException { Result result = new StreamResult(this.writer); result.setSystemId("id"); return result; } } /** Generate a JAXB unmarshal exception due to validation problems. */ private static InvalidDataException generateValidationError(Class type, ValidationEventCollector eventHandler) { InvalidDataException invalid = getUnmarshalError(type, null); invalid.getDetails().addMessage(ERROR_KEY, "Found validation errors"); for (ValidationEvent event : eventHandler.getEvents()) { invalid.getDetails().addMessage(ERROR_KEY, event.getMessage()); } return invalid; } /** Translate a JAXB unmarshal exception into something legible. */ private static InvalidDataException translateJaxbUnmarshalError(Class type, JAXBException e) { boolean added = false; InvalidDataException invalid = getUnmarshalError(type, e); // This was developed through trial-and-error. I don't know how well it will hold up in the future. if (e.getCause() != null && e.getLinkedException() != null) { try { throw e.getLinkedException(); } catch (org.xml.sax.SAXParseException sax) { String message = "Line " + sax.getLineNumber() + ", column " + sax.getColumnNumber() + ": " + e.getCause().getMessage(); invalid.getDetails().addMessage(ERROR_KEY, message); added = true; } catch (Throwable other) { } } if (!added && e.getCause() != null && e.getCause().getMessage() != null) { invalid.getDetails().addMessage(ERROR_KEY, e.getCause().getMessage()); added = true; } if (!added && e.getMessage() != null) { invalid.getDetails().addMessage(ERROR_KEY, e.getMessage()); added = true; } return invalid; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy