package org.hibernate.service.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationEventLocator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.internal.jaxb.Origin;
import org.hibernate.internal.jaxb.cfg.JaxbHibernateConfiguration;
import org.hibernate.internal.util.config.ConfigurationException;
import org.hibernate.metamodel.source.MappingException;
import org.hibernate.metamodel.source.XsdException;
import org.jboss.logging.Logger;
import org.xml.sax.SAXException;
public class JaxbProcessor {
private static final Logger log = Logger.getLogger( JaxbProcessor.class );
public static final String HIBERNATE_CONFIGURATION_URI = "http://www.hibernate.org/xsd/hibernate-configuration";
private final ClassLoaderService classLoaderService;
public JaxbProcessor(ClassLoaderService classLoaderService) {
this.classLoaderService = classLoaderService;
}
public JaxbHibernateConfiguration unmarshal(InputStream stream, Origin origin) {
try {
XMLEventReader staxReader = staxFactory().createXMLEventReader( stream );
try {
return unmarshal( staxReader, origin );
}
finally {
try {
staxReader.close();
}
catch ( Exception ignore ) {
}
}
}
catch ( XMLStreamException e ) {
throw new MappingException( "Unable to create stax reader", e, origin );
}
}
private XMLInputFactory staxFactory;
private XMLInputFactory staxFactory() {
if ( staxFactory == null ) {
staxFactory = buildStaxFactory();
}
return staxFactory;
}
@SuppressWarnings( { "UnnecessaryLocalVariable" })
private XMLInputFactory buildStaxFactory() {
XMLInputFactory staxFactory = XMLInputFactory.newInstance();
return staxFactory;
}
@SuppressWarnings( { "unchecked" })
private JaxbHibernateConfiguration unmarshal(XMLEventReader staxEventReader, final Origin origin) {
XMLEvent event;
try {
event = staxEventReader.peek();
while ( event != null && !event.isStartElement() ) {
staxEventReader.nextEvent();
event = staxEventReader.peek();
}
}
catch ( Exception e ) {
throw new MappingException( "Error accessing stax stream", e, origin );
}
if ( event == null ) {
throw new MappingException( "Could not locate root element", origin );
}
if ( !isNamespaced( event.asStartElement() ) ) {
log.debug( "cfg.xml document did not define namespaces; wrapping in custom event reader to introduce namespace information" );
staxEventReader = new NamespaceAddingEventReader( staxEventReader, HIBERNATE_CONFIGURATION_URI );
}
final Object target;
final ContextProvidingValidationEventHandler handler = new ContextProvidingValidationEventHandler();
try {
JAXBContext jaxbContext = JAXBContext.newInstance( JaxbHibernateConfiguration.class );
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema( schema() );
unmarshaller.setEventHandler( handler );
target = unmarshaller.unmarshal( staxEventReader );
return (JaxbHibernateConfiguration) target;
}
catch ( JAXBException e ) {
StringBuilder builder = new StringBuilder();
builder.append( "Unable to perform unmarshalling at line number " )
.append( handler.getLineNumber() )
.append( " and column " )
.append( handler.getColumnNumber() )
.append( " in " ).append( origin.getType().name() ).append( " " ).append( origin.getName() )
.append( ". Message: " )
.append( handler.getMessage() );
throw new ConfigurationException( builder.toString(), e );
}
}
private boolean isNamespaced(StartElement startElement) {
return ! "".equals( startElement.getName().getNamespaceURI() );
}
private Schema schema;
private Schema schema() {
if ( schema == null ) {
schema = resolveLocalSchema( "org/hibernate/hibernate-configuration-4.0.xsd" );
}
return schema;
}
private Schema resolveLocalSchema(String schemaName) {
return resolveLocalSchema( schemaName, XMLConstants.W3C_XML_SCHEMA_NS_URI );
}
private Schema resolveLocalSchema(String schemaName, String schemaLanguage) {
URL url = classLoaderService.locateResource( schemaName );
if ( url == null ) {
throw new XsdException( "Unable to locate schema [" + schemaName + "] via classpath", schemaName );
}
try {
InputStream schemaStream = url.openStream();
try {
StreamSource source = new StreamSource( url.openStream() );
SchemaFactory schemaFactory = SchemaFactory.newInstance( schemaLanguage );
return schemaFactory.newSchema( source );
}
catch ( SAXException e ) {
throw new XsdException( "Unable to load schema [" + schemaName + "]", e, schemaName );
}
catch ( IOException e ) {
throw new XsdException( "Unable to load schema [" + schemaName + "]", e, schemaName );
}
finally {
try {
schemaStream.close();
}
catch ( IOException e ) {
log.debugf( "Problem closing schema stream [%s]", e.toString() );
}
}
}
catch ( IOException e ) {
throw new XsdException( "Stream error handling schema url [" + url.toExternalForm() + "]", schemaName );
}
}
static class ContextProvidingValidationEventHandler implements ValidationEventHandler {
private int lineNumber;
private int columnNumber;
private String message;
@Override
public boolean handleEvent(ValidationEvent validationEvent) {
ValidationEventLocator locator = validationEvent.getLocator();
lineNumber = locator.getLineNumber();
columnNumber = locator.getColumnNumber();
message = validationEvent.getMessage();
return false;
}
public int getLineNumber() {
return lineNumber;
}
public int getColumnNumber() {
return columnNumber;
}
public String getMessage() {
return message;
}
}
public class NamespaceAddingEventReader extends EventReaderDelegate {
private final XMLEventFactory xmlEventFactory;
private final String namespaceUri;
public NamespaceAddingEventReader(XMLEventReader reader, String namespaceUri) {
this( reader, XMLEventFactory.newInstance(), namespaceUri );
}
public NamespaceAddingEventReader(XMLEventReader reader, XMLEventFactory xmlEventFactory, String namespaceUri) {
super( reader );
this.xmlEventFactory = xmlEventFactory;
this.namespaceUri = namespaceUri;
}
private StartElement withNamespace(StartElement startElement) {
final List<Namespace> namespaces = new ArrayList<Namespace>();
namespaces.add( xmlEventFactory.createNamespace( "", namespaceUri ) );
Iterator<?> originalNamespaces = startElement.getNamespaces();
while ( originalNamespaces.hasNext() ) {
namespaces.add( (Namespace) originalNamespaces.next() );
}
return xmlEventFactory.createStartElement(
new QName( namespaceUri, startElement.getName().getLocalPart() ),
startElement.getAttributes(),
namespaces.iterator()
);
}
@Override
public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = super.nextEvent();
if ( event.isStartElement() ) {
return withNamespace( event.asStartElement() );
}
return event;
}
@Override
public XMLEvent peek() throws XMLStreamException {
XMLEvent event = super.peek();
if ( event.isStartElement() ) {
return withNamespace( event.asStartElement() );
}
else {
return event;
}
}
}
}