diff --git a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java index dc239d91cd..9d87e07278 100644 --- a/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java +++ b/annotations/src/main/java/org/hibernate/cfg/AnnotationConfiguration.java @@ -26,6 +26,7 @@ package org.hibernate.cfg; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -54,6 +55,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ErrorHandler; import org.hibernate.AnnotationException; import org.hibernate.DuplicateMappingException; @@ -67,6 +70,7 @@ import org.hibernate.annotations.common.reflection.MetadataProviderInjector; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.java.JavaReflectionManager; +import org.hibernate.annotations.common.AssertionFailure; import org.hibernate.cfg.annotations.Version; import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; import org.hibernate.cfg.beanvalidation.BeanValidationActivator; @@ -839,32 +843,76 @@ public class AnnotationConfiguration extends Configuration { @Override public AnnotationConfiguration addInputStream(InputStream xmlInputStream) throws MappingException { try { - List errors = new ArrayList(); - SAXReader saxReader = xmlHelper.createSAXReader( "XML InputStream", errors, getEntityResolver() ); - try { - saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true ); - //saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true ); - //set the default schema locators - saxReader.setProperty( - "http://apache.org/xml/properties/schema/external-schemaLocation", - "http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd" - ); - } - catch ( SAXException e ) { - saxReader.setValidation( false ); - } - org.dom4j.Document doc = saxReader - .read( new InputSource( xmlInputStream ) ); + /* + * try and parse the document: + * - try and validate the document with orm_2_0.xsd + * - if it fails because of the version attribute mismatch, try and validate the document with orm_1_0.xsd + */ + List errors = new ArrayList(); + SAXReader saxReader = new SAXReader( ); + saxReader.setEntityResolver( getEntityResolver() ); + saxReader.setErrorHandler( new ErrorLogger(errors) ); + saxReader.setMergeAdjacentText(true); + saxReader.setValidation(true); + setValidationFor( saxReader, "orm_2_0.xsd" ); + + org.dom4j.Document doc = null; + try { + doc = saxReader.read( new InputSource( xmlInputStream ) ); + } + catch ( DocumentException e ) { + //the document is syntactically incorrect + + //DOM4J sometimes wraps the SAXParseException wo much interest + final Throwable throwable = e.getCause(); + if ( e.getCause() == null || !( throwable instanceof SAXParseException ) ) { + throw new MappingException( "Could not parse JPA mapping document", e ); + } + errors.add( (SAXParseException) throwable ); + } + + boolean isV1Schema = false; if ( errors.size() != 0 ) { - throw new MappingException( "invalid mapping", ( Throwable ) errors.get( 0 ) ); + SAXParseException exception = errors.get( 0 ); + final String errorMessage = exception.getMessage(); + //does the error look like a schema mismatch? + isV1Schema = doc != null + && errorMessage.contains("1.0") + && errorMessage.contains("2.0") + && errorMessage.contains("version"); + } + if (isV1Schema) { + //reparse with v1 + errors.clear(); + setValidationFor( saxReader, "orm_1_0.xsd" ); + try { + //too bad we have to reparse to validate again :( + saxReader.read( new StringReader( doc.asXML() ) ); + } + catch ( DocumentException e ) { + //oops asXML fails even if the core doc parses initially + new AssertionFailure("Error in DOM4J leads to a bug in Hibernate", e); + } + + } + if ( errors.size() != 0 ) { + //report errors in exception + StringBuilder errorMessage = new StringBuilder( ); + for (SAXParseException error : errors) { + errorMessage.append("Error parsing XML (line") + .append(error.getLineNumber()) + .append(" : column ") + .append(error.getColumnNumber()) + .append("): ") + .append(error.getMessage()) + .append("\n"); + } + throw new MappingException( "Invalid ORM mapping file.\n" + errorMessage.toString() ); } add( doc ); return this; } - catch ( DocumentException e ) { - throw new MappingException( "Could not parse mapping document in input stream", e ); - } finally { try { xmlInputStream.close(); @@ -875,6 +923,40 @@ public class AnnotationConfiguration extends Configuration { } } + private static class ErrorLogger implements ErrorHandler { + private List errors; + + public ErrorLogger(List errors) { + this.errors = errors; + } + + public void warning(SAXParseException exception) throws SAXException { + errors.add( exception ); + } + + public void error(SAXParseException exception) throws SAXException { + errors.add( exception ); + } + + public void fatalError(SAXParseException exception) throws SAXException { + } + } + + private void setValidationFor(SAXReader saxReader, String xsd) { + try { + saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true ); + //saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true ); + //set the default schema locators + 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 ); + } + } + public SessionFactory buildSessionFactory() throws HibernateException { enableLegacyHibernateValidator(); enableBeanValidation(); diff --git a/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm.xml b/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm.xml index 8d1083ee14..cf40c9b17c 100644 --- a/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm.xml +++ b/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm.xml @@ -1,9 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + version="2.0"> diff --git a/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm3.xml b/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm3.xml index 535cb57685..0d52ada445 100644 --- a/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm3.xml +++ b/annotations/src/test/resources/org/hibernate/test/annotations/xml/ejb3/orm3.xml @@ -2,9 +2,8 @@ + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm/orm_2_0.xsd" + version="2.0"> org.hibernate.test.annotations.xml.ejb3 diff --git a/entitymanager/src/main/java/org/hibernate/ejb/packaging/PersistenceXmlLoader.java b/entitymanager/src/main/java/org/hibernate/ejb/packaging/PersistenceXmlLoader.java index 90f64c44e5..833c242843 100644 --- a/entitymanager/src/main/java/org/hibernate/ejb/packaging/PersistenceXmlLoader.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/packaging/PersistenceXmlLoader.java @@ -68,6 +68,11 @@ public final class PersistenceXmlLoader { } private static Document loadURL(URL configURL, EntityResolver resolver) throws Exception { + /* + * try and parse the document: + * - try and validate the document with persistence_2_0.xsd + * - if it fails because of the version attribute mismatch, try and validate the document with persistence_1_0.xsd + */ InputStream is = null; if (configURL != null) { URLConnection conn = configURL.openConnection(); @@ -75,8 +80,9 @@ public final class PersistenceXmlLoader { is = conn.getInputStream(); } if ( is == null ) { - throw new IOException( "Failed to obtain InputStream from url: " + configURL ); + throw new IOException( "Failed to obtain InputStream while reading persistence.xml file: " + configURL ); } + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware( true ); final Schema v2Schema = SchemaFactory.newInstance( javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI ) @@ -91,6 +97,8 @@ public final class PersistenceXmlLoader { docBuilder.setEntityResolver( resolver ); List errors = new ArrayList(); Document doc = null; + + //first sparse document and collect syntaxic errors try { doc = docBuilder.parse( source ); } @@ -99,7 +107,7 @@ public final class PersistenceXmlLoader { } if (errors.size() == 0) { - v2Validator.setErrorHandler( new ErrorLogger( "XML InputStream", errors, resolver ) ); + v2Validator.setErrorHandler( new ErrorLogger( errors ) ); log.trace("Validate with persistence_2_0.xsd schema on file {}", configURL); v2Validator.validate( new DOMSource( doc ) ); boolean isV1Schema = false; @@ -108,6 +116,7 @@ public final class PersistenceXmlLoader { log.trace("Found error with persistence_2_0.xsd schema on file {}", configURL); SAXParseException exception = errors.get( 0 ); final String errorMessage = exception.getMessage(); + //is it a validation error due to a v1 schema validated by a v2 isV1Schema = errorMessage.contains("1.0") && errorMessage.contains("2.0") && errorMessage.contains("version"); @@ -116,11 +125,12 @@ public final class PersistenceXmlLoader { if (isV1Schema) { log.trace("Validate with persistence_1_0.xsd schema on file {}", configURL); errors.clear(); - v1Validator.setErrorHandler( new ErrorLogger( "XML InputStream", errors, resolver ) ); + v1Validator.setErrorHandler( new ErrorLogger( errors ) ); v1Validator.validate( new DOMSource( doc ) ); } } if ( errors.size() != 0 ) { + //report all errors in the exception StringBuilder errorMessage = new StringBuilder( ); for (SAXParseException error : errors) { errorMessage.append("Error parsing XML (line") @@ -310,17 +320,14 @@ public final class PersistenceXmlLoader { } public static class ErrorLogger implements ErrorHandler { - private String file; - private List errors; - private EntityResolver resolver; + private List errors; - ErrorLogger(String file, List errors, EntityResolver resolver) { - this.file = file; + ErrorLogger(List errors) { this.errors = errors; - this.resolver = resolver; } public void error(SAXParseException error) { + //what was this commented code about? // if ( resolver instanceof EJB3DTDEntityResolver ) { // if ( ( (EJB3DTDEntityResolver) resolver ).isResolved() == false ) return; // }