package org.hibernate.internal.util.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.hibernate.InvalidMappingException;
import org.hibernate.internal.CoreMessageLogger;
import org.jboss.logging.Logger;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.dom4j.io.STAXEventReader;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class MappingReader {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
MappingReader.class.getName()
);
public static final MappingReader INSTANCE = new MappingReader();
private MappingReader() {
}
public XmlDocument readMappingDocument(InputSource source, Origin origin) {
XMLEventReader staxReader = buildStaxEventReader( source, origin );
try {
return read( staxReader, origin );
}
finally {
try {
staxReader.close();
}
catch ( Exception ignore ) {
}
}
}
private XMLEventReader buildStaxEventReader(InputSource source, Origin origin) {
XMLEventReader reader = null;
if ( source.getByteStream() != null ) {
try {
reader = staxFactory().createXMLEventReader( source.getByteStream() );
}
catch (XMLStreamException e) {
throw new XmlInfrastructureException(
"Unable to create stax reader, origin = " + toLoggableString( origin ),
e
);
}
}
else if ( source.getCharacterStream() != null ) {
try {
reader = staxFactory().createXMLEventReader( source.getCharacterStream() );
}
catch (XMLStreamException e) {
throw new XmlInfrastructureException(
"Unable to create stax reader, origin = " + toLoggableString( origin ),
e
);
}
}
if ( reader == null ) {
throw new XmlInfrastructureException( "Unable to convert SAX InputStream into StAX XMLEventReader" );
}
return new BufferedXMLEventReader( reader );
}
private XMLInputFactory staxFactory;
private XMLInputFactory staxFactory() {
if ( staxFactory == null ) {
staxFactory = buildStaxFactory();
}
return staxFactory;
}
@SuppressWarnings( { "UnnecessaryLocalVariable" })
private XMLInputFactory buildStaxFactory() {
XMLInputFactory staxFactory = XMLInputFactory.newInstance();
staxFactory.setXMLResolver( LocalXmlResourceResolver.INSTANCE );
return staxFactory;
}
private String toLoggableString(Origin origin) {
return "[type=" + origin.getType() + ", name=" + origin.getName() + "]";
}
private static final QName ORM_VERSION_ATTRIBUTE_QNAME = new QName( "version" );
private XmlDocument read(XMLEventReader staxEventReader, Origin origin) {
XMLEvent event;
try {
event = staxEventReader.peek();
while ( event != null && !event.isStartElement() ) {
staxEventReader.nextEvent();
event = staxEventReader.peek();
}
}
catch ( Exception e ) {
throw new InvalidMappingException( "Error accessing stax stream", origin, e );
}
if ( event == null ) {
throw new InvalidMappingException( "Could not locate root element", origin );
}
final String rootElementName = event.asStartElement().getName().getLocalPart();
if ( "entity-mappings".equals( rootElementName ) ) {
final Attribute attribute = event.asStartElement().getAttributeByName( ORM_VERSION_ATTRIBUTE_QNAME );
final String explicitVersion = attribute == null ? null : attribute.getValue();
validateMapping(
SupportedOrmXsdVersion.parse( explicitVersion, origin ),
staxEventReader,
origin
);
}
return new XmlDocumentImpl( toDom4jDocument( staxEventReader, origin ), origin );
}
private Document toDom4jDocument(XMLEventReader staxEventReader, Origin origin) {
STAXEventReader dom4jStaxEventReader = new STAXEventReader();
try {
staxEventReader = new FilteringXMLEventReader( staxEventReader ) {
@Override
protected XMLEvent filterEvent(XMLEvent event, boolean peek) {
return event.getEventType() == XMLStreamConstants.COMMENT
? null
: event;
}
};
return dom4jStaxEventReader.readDocument( staxEventReader );
}
catch (XMLStreamException e) {
throw new InvalidMappingException( "Unable to read StAX source as dom4j Document for processing", origin, e );
}
}
private void validateMapping(SupportedOrmXsdVersion xsdVersion, XMLEventReader staxEventReader, Origin origin) {
final Validator validator = xsdVersion.getSchema().newValidator();
final StAXSource staxSource;
try {
staxSource = new StAXSource( staxEventReader );
}
catch (XMLStreamException e) {
throw new InvalidMappingException( "Unable to generate StAXSource from mapping", origin, e );
}
try {
validator.validate( staxSource );
}
catch (SAXException e) {
throw new InvalidMappingException( "SAXException performing validation", origin, e );
}
catch (IOException e) {
throw new InvalidMappingException( "IOException performing validation", origin, e );
}
}
public static enum SupportedOrmXsdVersion {
ORM_1_0( "org/hibernate/jpa/orm_1_0.xsd" ),
ORM_2_0( "org/hibernate/jpa/orm_2_0.xsd" ),
ORM_2_1( "org/hibernate/jpa/orm_2_1.xsd" );
private final String schemaResourceName;
private SupportedOrmXsdVersion(String schemaResourceName) {
this.schemaResourceName = schemaResourceName;
}
public static SupportedOrmXsdVersion parse(String name, Origin origin) {
if ( "1.0".equals( name ) ) {
return ORM_1_0;
}
else if ( "2.0".equals( name ) ) {
return ORM_2_0;
}
else if ( "2.1".equals( name ) ) {
return ORM_2_1;
}
throw new UnsupportedOrmXsdVersionException( name, origin );
}
private URL schemaUrl;
public URL getSchemaUrl() {
if ( schemaUrl == null ) {
schemaUrl = resolveLocalSchemaUrl( schemaResourceName );
}
return schemaUrl;
}
private Schema schema;
public Schema getSchema() {
if ( schema == null ) {
schema = resolveLocalSchema( getSchemaUrl() );
}
return schema;
}
}
private static URL resolveLocalSchemaUrl(String schemaName) {
URL url = MappingReader.class.getClassLoader().getResource( schemaName );
if ( url == null ) {
throw new XmlInfrastructureException( "Unable to locate schema [" + schemaName + "] via classpath" );
}
return url;
}
private static Schema resolveLocalSchema(URL schemaUrl) {
try {
InputStream schemaStream = schemaUrl.openStream();
try {
StreamSource source = new StreamSource(schemaUrl.openStream());
SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
return schemaFactory.newSchema(source);
}
catch ( Exception e ) {
throw new XmlInfrastructureException( "Unable to load schema [" + schemaUrl.toExternalForm() + "]", e );
}
finally {
try {
schemaStream.close();
}
catch ( IOException e ) {
LOG.debugf( "Problem closing schema stream - %s", e.toString() );
}
}
}
catch ( IOException e ) {
throw new XmlInfrastructureException( "Stream error handling schema url [" + schemaUrl.toExternalForm() + "]" );
}
}
public XmlDocument readMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) {
return legacyReadMappingDocument( entityResolver, source, origin );
}
private XmlDocument legacyReadMappingDocument(EntityResolver entityResolver, InputSource source, Origin origin) {
Exception failure;
ErrorLogger errorHandler = new ErrorLogger();
SAXReader saxReader = new SAXReader();
saxReader.setEntityResolver( entityResolver );
saxReader.setErrorHandler( errorHandler );
saxReader.setMergeAdjacentText( true );
saxReader.setValidation( true );
Document document = null;
try {
setValidationFor( saxReader, "orm_2_1.xsd" );
document = saxReader.read( source );
if ( errorHandler.hasErrors() ) {
throw errorHandler.getErrors().get( 0 );
}
return new XmlDocumentImpl( document, origin.getType(), origin.getName() );
}
catch ( Exception e ) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Problem parsing XML using orm 2.1 xsd, trying 2.0 xsd : %s", e.getMessage() );
}
failure = e;
errorHandler.reset();
if ( document != null ) {
try {
setValidationFor( saxReader, "orm_2_0.xsd" );
document = saxReader.read( new StringReader( document.asXML() ) );
if ( errorHandler.hasErrors() ) {
errorHandler.logErrors();
throw errorHandler.getErrors().get( 0 );
}
return new XmlDocumentImpl( document, origin.getType(), origin.getName() );
}
catch ( Exception e2 ) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Problem parsing XML using orm 2.0 xsd, trying 1.0 xsd : %s", e2.getMessage() );
}
errorHandler.reset();
if ( document != null ) {
try {
setValidationFor( saxReader, "orm_1_0.xsd" );
document = saxReader.read( new StringReader( document.asXML() ) );
if ( errorHandler.hasErrors() ) {
errorHandler.logErrors();
throw errorHandler.getErrors().get( 0 );
}
return new XmlDocumentImpl( document, origin.getType(), origin.getName() );
}
catch ( Exception e3 ) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Problem parsing XML using orm 1.0 xsd : %s", e3.getMessage() );
}
}
}
}
}
}
throw new InvalidMappingException( "Unable to read XML", origin.getType(), origin.getName(), failure );
}
private void setValidationFor(SAXReader saxReader, String xsd) {
try {
saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true );
if ( "orm_2_1.xsd".equals( xsd ) ) {
saxReader.setProperty(
"http://apache.org/xml/properties/schema/external-schemaLocation",
"http://xmlns.jcp.org/xml/ns/persistence/orm " + xsd
);
}
else {
saxReader.setProperty(
"http://apache.org/xml/properties/schema/external-schemaLocation",
"http://java.sun.com/xml/ns/persistence/orm " + xsd
);
}
}
catch ( SAXException e ) {
saxReader.setValidation( false );
}
}
}