diff --git a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java index 0d86037ae7f4..e94d0066d886 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java @@ -22,6 +22,7 @@ import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.type.format.FormatMapper; +import org.hibernate.boot.xsd.XmlValidationMode; import jakarta.persistence.criteria.Nulls; @@ -755,6 +756,18 @@ public interface SessionFactoryBuilder { @Incubating SessionFactoryBuilder applyXmlFormatMapper(FormatMapper xmlFormatMapper); + /** + * Specifies a {@link XmlValidationMode validation mode} to use for validation of XML files. + * + * @param xmlValidationMode The {@link XmlValidationMode} to use. + * + * @return {@code this}, for method chaining + * + * @see org.hibernate.cfg.AvailableSettings#XML_VALIDATION_MODE + */ + @Incubating + SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode); + /** * After all options have been set, build the SessionFactory. * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/RepeatableInputStreamAccess.java b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/RepeatableInputStreamAccess.java new file mode 100644 index 000000000000..1a30574f882a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/RepeatableInputStreamAccess.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.archive.internal; + + +import org.hibernate.HibernateException; +import org.hibernate.boot.archive.spi.InputStreamAccess; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Jan Schatteman + */ +public class RepeatableInputStreamAccess implements InputStreamAccess { + + private final String resourceName; + private byte[] bytes = new byte[0]; + + public RepeatableInputStreamAccess(String resourceName, InputStream inputStream) { + if ( inputStream == null ) { + throw new HibernateException( "InputStream is null for resource " + resourceName ); + } + this.resourceName = resourceName; + try { + bytes = inputStream.readAllBytes(); + } + catch (IOException | OutOfMemoryError e) { + throw new HibernateException( "Could not read resource " + resourceName, e ); + } + } + + @Override + public String getStreamName() { + return resourceName; + } + + @Override + public InputStream accessInputStream() { + return new ByteArrayInputStream( bytes ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index d76cbf40f42c..e3e16ef6aecd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -18,6 +18,7 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryBuilderImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.bytecode.internal.SessionFactoryObserverForBytecodeEnhancer; import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.cache.spi.TimestampsCacheFactory; @@ -431,6 +432,12 @@ public void disableJtaTransactionAccess() { this.optionsBuilder.disableJtaTransactionAccess(); } + @Override + public SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode) { + this.optionsBuilder.applyXmlValidationMode( xmlValidationMode ); + return this; + } + @Override public SessionFactory build() { return new SessionFactoryImpl( metadata, buildSessionFactoryOptions(), bootstrapContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index db3031ebeb36..47ce6dc04a87 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -42,6 +42,7 @@ import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.cache.internal.NoCachingRegionFactory; import org.hibernate.cache.internal.StandardTimestampsCacheFactory; import org.hibernate.cache.spi.RegionFactory; @@ -240,6 +241,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final int queryStatisticsMaxSize; + private XmlValidationMode xmlValidationMode; private final Map defaultSessionProperties; private final CacheStoreMode defaultCacheStoreMode; private final CacheRetrieveMode defaultCacheRetrieveMode; @@ -538,6 +540,8 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo defaultLockOptions = defaultLockOptions( defaultSessionProperties ); initialSessionFlushMode = defaultFlushMode( defaultSessionProperties ); + + xmlValidationMode = ConfigurationHelper.resolveXmlValidationMode( settings ); } @Deprecated(forRemoval = true) @@ -1409,6 +1413,9 @@ public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() { return preferJdbcDatetimeTypes; } + @Override + public XmlValidationMode getXmlValidationMode() { return xmlValidationMode; } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access @@ -1686,7 +1693,6 @@ public void enableGeneratorNameScopeCompliance(boolean enabled) { mutableJpaCompliance().setGeneratorNameScopeCompliance( enabled ); } - public void enableCollectionInDefaultFetchGroup(boolean enabled) { this.collectionsInDefaultFetchGroupEnabled = enabled; } @@ -1695,6 +1701,10 @@ public void disableJtaTransactionAccess() { this.jtaTransactionAccessEnabled = false; } + public void applyXmlValidationMode(XmlValidationMode xmlValidationMode) { + this.xmlValidationMode = xmlValidationMode; + } + public SessionFactoryOptions buildOptions() { if ( jpaCompliance instanceof MutableJpaCompliance mutableJpaCompliance ) { jpaCompliance = mutableJpaCompliance.immutableCopy(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java index 6b2cc82f699e..488388c8d151 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java @@ -13,8 +13,11 @@ import javax.xml.transform.Source; import javax.xml.validation.Schema; +import org.hibernate.HibernateException; import org.hibernate.boot.MappingException; import org.hibernate.boot.ResourceStreamLocator; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; +import org.hibernate.boot.archive.spi.InputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.internal.stax.BufferedXMLEventReader; import org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver; @@ -40,17 +43,20 @@ protected AbstractBinder(ResourceStreamLocator resourceStreamLocator) { this.xmlResourceResolver = new LocalXmlResourceResolver( resourceStreamLocator ); } - public abstract boolean isValidationEnabled(); - @Override public Binding bind(InputStream stream, Origin origin) { - final XMLEventReader eventReader = createReader( stream, origin ); + return bind( new RepeatableInputStreamAccess(origin.getName(), stream), origin ); + } + + @Override + public Binding bind(InputStreamAccess streamAccess, Origin origin) { + final JaxbBindingSource jaxbBindingSource = createReader( streamAccess, origin ); try { - return doBind( eventReader, origin ); + return doBind( jaxbBindingSource ); } finally { try { - eventReader.close(); + jaxbBindingSource.getEventReader().close(); } catch (XMLStreamException e) { log.debug( "Unable to close StAX reader", e ); @@ -58,44 +64,46 @@ public Binding bind(InputStream stream, Origin origin) { } } - protected XMLEventReader createReader(InputStream stream, Origin origin) { - try { - // create a standard StAX reader - final XMLEventReader staxReader = staxFactory().createXMLEventReader( stream ); - // and wrap it in a buffered reader (keeping 100 element sized buffer) - return new BufferedXMLEventReader( staxReader, 100 ); - } - catch ( XMLStreamException e ) { - throw new MappingException( "Unable to create StAX reader", e, origin ); - } + protected JaxbBindingSource createReader(InputStreamAccess streamAccess, Origin origin) { + return new JaxbBindingSource() { + @Override + public Origin getOrigin() { + return origin; + } + + @Override + public InputStreamAccess getInputStreamAccess() { + return streamAccess; + } + + @Override + public XMLEventReader getEventReader() { + try { + // create a standard StAX reader + final XMLEventReader staxReader = staxFactory().createXMLEventReader( streamAccess.accessInputStream() ); + // and wrap it in a buffered reader (keeping 100 element sized buffer) + return new BufferedXMLEventReader( staxReader, 100 ); + } + catch ( XMLStreamException e ) { + throw new MappingException( "Unable to create StAX reader", e, origin ); + } + } + }; } @Override public Binding bind(Source source, Origin origin) { - final XMLEventReader eventReader = createReader( source, origin ); - return doBind( eventReader, origin ); + throw new HibernateException( "The Binder.bind(Source, Origin) method is no longer supported" ); } - protected XMLEventReader createReader(Source source, Origin origin) { + private Binding doBind(JaxbBindingSource jaxbBindingSource) { try { - // create a standard StAX reader - final XMLEventReader staxReader = staxFactory().createXMLEventReader( source ); - // and wrap it in a buffered reader (keeping 100 element sized buffer) - return new BufferedXMLEventReader( staxReader, 100 ); - } - catch ( XMLStreamException e ) { - throw new MappingException( "Unable to create StAX reader", e, origin ); - } - } - - private Binding doBind(XMLEventReader eventReader, Origin origin) { - try { - final StartElement rootElementStartEvent = seekRootElementStartEvent( eventReader, origin ); - return doBind( eventReader, rootElementStartEvent, origin ); + final StartElement rootElementStartEvent = seekRootElementStartEvent( jaxbBindingSource.getEventReader(), jaxbBindingSource.getOrigin() ); + return doBind( jaxbBindingSource, rootElementStartEvent ); } finally { try { - eventReader.close(); + jaxbBindingSource.getEventReader().close(); } catch (Exception e) { log.debug( "Unable to close StAX reader", e ); @@ -138,7 +146,7 @@ protected StartElement seekRootElementStartEvent(XMLEventReader staxEventReader, return rootElementStartEvent.asStartElement(); } - protected abstract Binding doBind(XMLEventReader staxEventReader, StartElement rootElementStartEvent, Origin origin); + protected abstract Binding doBind(JaxbBindingSource jaxbBindingSource, StartElement rootElementStartEvent); @SuppressWarnings("unused") protected static boolean hasNamespace(StartElement startElement) { @@ -150,12 +158,7 @@ protected X jaxb(XMLEventReader reader, Schema xsd, JAXBContext ja try { final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - if ( isValidationEnabled() ) { - unmarshaller.setSchema( xsd ); - } - else { - unmarshaller.setSchema( null ); - } + unmarshaller.setSchema( xsd ); unmarshaller.setEventHandler( handler ); //noinspection unchecked @@ -172,5 +175,4 @@ protected X jaxb(XMLEventReader reader, Schema xsd, JAXBContext ja } } - } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/ConfigurationBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/ConfigurationBinder.java index ea5f3962e99f..add691ad0720 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/ConfigurationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/ConfigurationBinder.java @@ -7,19 +7,21 @@ import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.StartElement; +import javax.xml.validation.Schema; import org.hibernate.Internal; import org.hibernate.boot.ResourceStreamLocator; -import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.configuration.spi.JaxbPersistenceImpl; import org.hibernate.boot.jaxb.internal.stax.ConfigurationEventReader; import org.hibernate.boot.jaxb.spi.Binding; import org.hibernate.boot.xsd.ConfigXsdSupport; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.internal.util.config.ConfigurationException; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; + /** * @author Steve Ebersole */ @@ -31,25 +33,33 @@ public ConfigurationBinder(ResourceStreamLocator resourceStreamLocator) { super( resourceStreamLocator ); } - @Override - public boolean isValidationEnabled() { - return false; + protected XmlValidationMode getXmlValidationMode() { + return XmlValidationMode.DISABLED; } @Override protected Binding doBind( - XMLEventReader staxEventReader, - StartElement rootElementStartEvent, - Origin origin) { - final XMLEventReader reader = new ConfigurationEventReader( staxEventReader, xmlEventFactory ); + JaxbBindingSource jaxbBindingSource, + StartElement rootElementStartEvent) { + final XMLEventReader reader = new ConfigurationEventReader( jaxbBindingSource.getEventReader(), xmlEventFactory ); + + final Schema xsd; + // evaluate extended (the former validate_xml 'true') in case anyone should override getXmlValidationMode() to switch it on + if ( getXmlValidationMode() == XmlValidationMode.EXTENDED ) { + xsd = ConfigXsdSupport.configurationXsd().getSchema(); + } + else { + xsd = null; + } + final JaxbPersistenceImpl bindingRoot = jaxb( reader, - ConfigXsdSupport.configurationXsd().getSchema(), + xsd, jaxbContext(), - origin + jaxbBindingSource.getOrigin() ); //noinspection unchecked - return new Binding<>( (X) bindingRoot, origin ); + return new Binding<>( (X) bindingRoot, jaxbBindingSource.getOrigin() ); } @Internal diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamAccessXmlSource.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamAccessXmlSource.java index 9d97286e88c4..bd5692828cd7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamAccessXmlSource.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamAccessXmlSource.java @@ -27,8 +27,6 @@ public Binding doBind(Binder binder) { } public static Binding doBind(Binder binder, InputStreamAccess inputStreamAccess, Origin origin) { - return inputStreamAccess.fromStream( - inputStream -> binder.bind( inputStream, origin ) - ); + return binder.bind( inputStreamAccess, origin ) ; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamXmlSource.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamXmlSource.java index c7ad607f2ec4..abe34528ab2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamXmlSource.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamXmlSource.java @@ -8,6 +8,7 @@ import java.io.InputStream; import org.hibernate.boot.InvalidMappingException; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.spi.Binder; import org.hibernate.boot.jaxb.spi.Binding; @@ -38,7 +39,7 @@ public Binding doBind(Binder binder) { public static Binding doBind(Binder binder, InputStream inputStream, Origin origin, boolean autoClose) { try { - return binder.bind( inputStream, origin ); + return binder.bind( new RepeatableInputStreamAccess( origin.getName(), inputStream), origin ); } catch ( Exception e ) { throw new InvalidMappingException( origin, e ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxbBindingSource.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxbBindingSource.java new file mode 100644 index 000000000000..0a85c7645da7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxbBindingSource.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.jaxb.internal; + + +import org.hibernate.boot.archive.spi.InputStreamAccess; +import org.hibernate.boot.jaxb.Origin; + +import javax.xml.stream.XMLEventReader; + +/** + * @author Jan Schatteman + */ +public interface JaxbBindingSource { + Origin getOrigin(); + InputStreamAccess getInputStreamAccess(); + XMLEventReader getEventReader(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxpSourceXmlSource.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxpSourceXmlSource.java deleted file mode 100644 index bb42a14ee221..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/JaxpSourceXmlSource.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.jaxb.internal; - -import javax.xml.transform.Source; - -import org.hibernate.boot.jaxb.Origin; -import org.hibernate.boot.jaxb.spi.Binder; -import org.hibernate.boot.jaxb.spi.Binding; -import org.hibernate.boot.jaxb.spi.XmlSource; - -/** - * @author Steve Ebersole - */ -public class JaxpSourceXmlSource extends XmlSource { - private final Source jaxpSource; - - public JaxpSourceXmlSource(Origin origin, Source jaxpSource) { - super( origin ); - this.jaxpSource = jaxpSource; - } - - @Override - public Binding doBind(Binder binder) { - return binder.bind( jaxpSource, getOrigin() ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java index 78ef6bc753ec..9edadabd1423 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/MappingBinder.java @@ -4,13 +4,17 @@ */ package org.hibernate.boot.jaxb.internal; -import java.util.function.Function; +import java.util.Map; import java.util.function.Supplier; import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventReader; import javax.xml.stream.events.StartElement; +import javax.xml.transform.stax.StAXSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; import org.hibernate.Internal; +import org.hibernate.boot.MappingException; import org.hibernate.boot.ResourceStreamLocator; import org.hibernate.boot.UnsupportedOrmXsdVersionException; import org.hibernate.boot.jaxb.Origin; @@ -24,9 +28,10 @@ import org.hibernate.boot.jaxb.spi.JaxbBindableMappingDescriptor; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.xsd.MappingXsdSupport; -import org.hibernate.cfg.AvailableSettings; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.internal.util.config.ConfigurationException; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -35,8 +40,9 @@ import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; import org.checkerframework.checker.nullness.qual.Nullable; - -import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; /** * Responsible for coordinating binding of mapping XML documents into @@ -55,22 +61,14 @@ public class MappingBinder extends AbstractBinder private JAXBContext entityMappingsJaxbContext; public interface Options { - boolean validateMappings(); + XmlValidationMode validationMode(); } - public static final Options VALIDATING = new Options() { - @Override - public boolean validateMappings() { - return true; - } - }; + public static final Options NON_VALIDATING = () -> XmlValidationMode.DISABLED; - public static final Options NON_VALIDATING = new Options() { - @Override - public boolean validateMappings() { - return true; - } - }; + public static final Options DEFAULT_VALIDATING = () -> XmlValidationMode.EXTENDED; + + public static final Options STRICT_VALIDATING = () -> XmlValidationMode.STRICT; /** * Full constructor @@ -95,35 +93,26 @@ private MappingBinder( public MappingBinder( ResourceStreamLocator resourceStreamLocator, - @Nullable Function settingsAccess) { + @Nullable Supplier> settingsAccess) { super( resourceStreamLocator == null ? MappingBinder.class.getClassLoader()::getResourceAsStream : resourceStreamLocator ); if ( settingsAccess == null ) { - this.optionsAccess = () -> VALIDATING; + this.optionsAccess = () -> DEFAULT_VALIDATING; } else { - this.optionsAccess = () -> new Options() { - @Override - public boolean validateMappings() { - final Object setting = settingsAccess.apply( AvailableSettings.VALIDATE_XML ); - if ( setting == null ) { - return false; - } - return BOOLEAN.convert( setting ); - } - }; + this.optionsAccess = () -> (Options) () -> ConfigurationHelper.resolveXmlValidationMode( settingsAccess.get() ); } } public MappingBinder(ServiceRegistry serviceRegistry) { this( serviceRegistry.getService( ClassLoaderService.class ), - (settingName) -> { + (Supplier>) () -> { final ConfigurationService configurationService = serviceRegistry instanceof ServiceRegistryImplementor serviceRegistryImplementor ? serviceRegistryImplementor.fromRegistryOrChildren( ConfigurationService.class ) : serviceRegistry.getService( ConfigurationService.class ); - return configurationService == null ? null : configurationService.getSettings().get( settingName ); + return configurationService == null ? null : configurationService.getSettings(); } ); } @@ -134,12 +123,7 @@ public MappingBinder(ServiceRegistry serviceRegistry) { public MappingBinder(ResourceStreamLocator resourceStreamLocator, UnsupportedFeatureHandling unsupportedHandling) { this( resourceStreamLocator, - new Options() { - @Override - public boolean validateMappings() { - return false; - } - }, + MappingBinder.NON_VALIDATING, unsupportedHandling ); } @@ -151,26 +135,35 @@ public MappingBinder(ResourceStreamLocator resourceStreamLocator, Options option this( resourceStreamLocator, options, UnsupportedFeatureHandling.ERROR ); } - @Override - public boolean isValidationEnabled() { - return optionsAccess.get().validateMappings(); + protected XmlValidationMode getXmlValidationMode() { + return optionsAccess.get().validationMode(); } @Override protected Binding doBind( - XMLEventReader staxEventReader, - StartElement rootElementStartEvent, - Origin origin) { + JaxbBindingSource jaxbBindingSource, + StartElement rootElementStartEvent) { final String rootElementLocalName = rootElementStartEvent.getName().getLocalPart(); + final XMLEventReader staxEventReader = jaxbBindingSource.getEventReader(); + final Origin origin = jaxbBindingSource.getOrigin(); if ( "hibernate-mapping".equals( rootElementLocalName ) ) { if ( log.isTraceEnabled() ) { log.tracef( "Performing JAXB binding of hbm.xml document: %s", origin.toString() ); } final XMLEventReader hbmReader = new HbmEventReader( staxEventReader, xmlEventFactory ); + + final Schema xsd; + if ( getXmlValidationMode() == XmlValidationMode.EXTENDED ) { + xsd = MappingXsdSupport.INSTANCE.hbmXsd().getSchema(); + } + else { + xsd = null; + } + final JaxbHbmHibernateMapping hbmBindings = jaxb( hbmReader, - MappingXsdSupport.INSTANCE.hbmXsd().getSchema(), + xsd, hbmJaxbContext(), origin ); @@ -184,9 +177,25 @@ protected Binding doBind( log.tracef( "Performing JAXB binding of orm.xml document: %s", origin.toString() ); final XMLEventReader reader = new MappingEventReader( staxEventReader, xmlEventFactory ); + + final Schema xsd; + final XmlValidationMode validationMode = getXmlValidationMode(); + if ( validationMode == XmlValidationMode.STRICT ) { + // deals with StrictValidationErrorHandler, etc + doStrictValidation( jaxbBindingSource ); + xsd = null; + } + else if ( validationMode == XmlValidationMode.EXTENDED ) { + // extended validation + xsd = MappingXsdSupport.latestDescriptor().getSchema(); + } + else { + xsd = null; + } + final JaxbEntityMappingsImpl bindingRoot = jaxb( reader, - MappingXsdSupport.latestDescriptor().getSchema(), + xsd, mappingJaxbContext(), origin ); @@ -200,6 +209,23 @@ protected Binding doBind( } } + private void doStrictValidation(JaxbBindingSource jaxbBindingSource) { + StrictValidationErrorHandler errorHandler = new StrictValidationErrorHandler(); + try { + Validator validator = MappingXsdSupport.latestJpaDescriptor().getSchema() + .newValidator(); + validator.setErrorHandler( errorHandler ); + // We need 'clean' access to the InputStream at this point, using the staxEventReader leads to errors + validator.validate( new StAXSource(jaxbBindingSource.getEventReader()) ); + } + catch (Exception e) { + throw new MappingException( + "Strict validation failure: " + errorHandler.getMessage(), + e, + jaxbBindingSource.getOrigin() ); + } + } + private JAXBContext hbmJaxbContext() { if ( hbmJaxbContext == null ) { try { @@ -224,4 +250,33 @@ public JAXBContext mappingJaxbContext() { } return entityMappingsJaxbContext; } + + private static final class StrictValidationErrorHandler implements ErrorHandler { + private int lineNumber; + private int columnNumber; + private String message; + + @Override + public void warning(SAXParseException exception) {} + + @Override + // Capture validation errors + public void error(SAXParseException exception) throws SAXException { + lineNumber = exception.getLineNumber(); + columnNumber = exception.getColumnNumber(); + message = exception.toString(); + throw new SAXException( getMessage() ); + } + + @Override + public void fatalError(SAXParseException exception) { + } + + public String getMessage() { + StringBuilder sb = new StringBuilder(); + sb.append( message ).append( lineNumber != -1 && columnNumber != -1 ? " at line number " + lineNumber + " and column number " + columnNumber : "" ); + return sb.toString(); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/XmlSources.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/XmlSources.java index fdc653f33fdd..00a82d976cca 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/XmlSources.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/XmlSources.java @@ -12,7 +12,6 @@ import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import javax.xml.transform.dom.DOMSource; import org.hibernate.boot.MappingNotFoundException; import org.hibernate.boot.archive.spi.InputStreamAccess; @@ -21,7 +20,6 @@ import org.hibernate.boot.jaxb.spi.XmlSource; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.w3c.dom.Document; import static org.hibernate.boot.jaxb.JaxbLogger.JAXB_LOGGER; @@ -109,12 +107,6 @@ public static XmlSource fromStream(InputStream inputStream) { return new InputStreamXmlSource( origin, inputStream, false ); } - public static XmlSource fromDocument(Document document) { - JAXB_LOGGER.trace( "reading mappings from DOM" ); - final Origin origin = new Origin( SourceType.DOM, Origin.UNKNOWN_FILE_PATH ); - return new JaxpSourceXmlSource( origin, new DOMSource( document ) ); - } - /** * Read all {@code .hbm.xml} mappings from a jar file and pass them * to the given {@link Consumer}. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/spi/Binder.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/spi/Binder.java index 602c3f05cf30..320d94ddd331 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/spi/Binder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/spi/Binder.java @@ -4,11 +4,13 @@ */ package org.hibernate.boot.jaxb.spi; -import java.io.InputStream; import javax.xml.transform.Source; +import org.hibernate.boot.archive.spi.InputStreamAccess; import org.hibernate.boot.jaxb.Origin; +import java.io.InputStream; + /** * Contract for performing JAXB binding. * @@ -21,7 +23,9 @@ public interface Binder { * @param source The XML source. * @param origin The descriptor of the source origin * @return The bound JAXB model + * @deprecated Use {@link #bind(InputStreamAccess, Origin)} instead */ + @Deprecated(since = "7.2") Binding bind(Source source, Origin origin); /** @@ -30,6 +34,17 @@ public interface Binder { * @param stream The InputStream containing XML * @param origin The descriptor of the stream origin * @return The bound JAXB model + * @deprecated Use {@link #bind(InputStreamAccess, Origin)} instead */ + @Deprecated(since = "7.2") Binding bind(InputStream stream, Origin origin); + + /** + * Bind from an InputStreamAccess + * + * @param streamAccess The {@link InputStreamAccess} providing access to the stream containing XML + * @param origin The descriptor of the stream origin + * @return The bound JAXB model + */ + Binding bind(InputStreamAccess streamAccess, Origin origin); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index cb590a258b49..4f117997689f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -24,6 +24,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.Internal; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.internal.InFlightMetadataCollectorImpl; import org.hibernate.boot.internal.MetadataBuildingContextRootImpl; import org.hibernate.boot.internal.RootMappingDefaults; @@ -104,6 +105,7 @@ import jakarta.persistence.AttributeConverter; +import static org.hibernate.boot.xsd.XmlValidationMode.DISABLED; import static org.hibernate.cfg.MappingSettings.XML_MAPPING_ENABLED; import static org.hibernate.internal.util.collections.CollectionHelper.mutableJoin; import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray; @@ -494,7 +496,7 @@ private static void processAdditionalMappingContributions( final AdditionalMappingContributionsImpl contributions = new AdditionalMappingContributionsImpl( metadataCollector, options, - options.isXmlMappingEnabled() ? new MappingBinder( classLoaderService, () -> false ) : null, + options.isXmlMappingEnabled() ? new MappingBinder( classLoaderService, () -> DISABLED ) : null, rootMetadataBuildingContext ); @@ -570,7 +572,7 @@ public void contributeManagedClass(ClassDetails classDetails) { @Override public void contributeBinding(InputStream xmlStream) { final Origin origin = new Origin( SourceType.INPUT_STREAM, null ); - final Binding binding = mappingBinder.bind( xmlStream, origin ); + final Binding binding = mappingBinder.bind( new RepeatableInputStreamAccess( SourceType.INPUT_STREAM.toString(), xmlStream ), origin ); final JaxbBindableMappingDescriptor bindingRoot = binding.getRoot(); if ( bindingRoot instanceof JaxbHbmHibernateMapping hibernateMapping ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AdditionalManagedResourcesImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AdditionalManagedResourcesImpl.java index 0c5b92199f09..81066f441e9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AdditionalManagedResourcesImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/annotations/AdditionalManagedResourcesImpl.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -145,7 +146,10 @@ public Builder addXmlMappings(String resourceName) { } public Builder addXmlMappings(String resourceName, Origin origin) { - return addXmlBinding( mappingBinder.bind( getResourceAsStream( resourceName ), origin ) ); + return addXmlBinding( mappingBinder.bind( + new RepeatableInputStreamAccess( resourceName, getResourceAsStream( resourceName ) ), + origin + ) ); } public Builder addXmlBinding(Binding binding) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java index 3f82b67a1177..c2a93954c249 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java @@ -14,6 +14,7 @@ import org.hibernate.annotations.CacheLayout; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.TempTableDdlTransactionHandling; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.context.spi.TenantSchemaMapper; @@ -409,6 +410,12 @@ public T applyXmlFormatMapper(FormatMapper xmlFormatMapper) { return getThis(); } + @Override + public T applyXmlValidationMode(XmlValidationMode xmlValidationMode) { + delegate.applyXmlValidationMode( xmlValidationMode ); + return getThis(); + } + @Override public SessionFactory build() { return delegate.build(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 016b41807cca..3cdd3cc1eb21 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -25,6 +25,7 @@ import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.internal.BaselineSessionEventsListenerBuilder; @@ -595,4 +596,10 @@ public FlushMode getInitialSessionFlushMode() { public LockOptions getDefaultLockOptions() { return delegate.getDefaultLockOptions(); } + + @Override + public XmlValidationMode getXmlValidationMode() { + return delegate.getXmlValidationMode(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 63ae89317a6d..07b71f2a6588 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -26,6 +26,7 @@ import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.cache.spi.TimestampsCache; import org.hibernate.cache.spi.TimestampsCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; @@ -790,4 +791,12 @@ default JavaType getDefaultTenantIdentifierJavaType() { * @see org.hibernate.Session#setProperty(String, Object) */ Map getDefaultSessionProperties(); + + /** + * @see org.hibernate.cfg.MappingSettings#XML_VALIDATION_MODE + */ + default XmlValidationMode getXmlValidationMode() { + return XmlValidationMode.DISABLED; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java index 896f6d1ee9c6..5318ad8a4fb2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java @@ -8,7 +8,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.function.Function; +import java.util.Map; +import java.util.function.Supplier; import org.hibernate.boot.MappingNotFoundException; import org.hibernate.boot.archive.spi.InputStreamAccess; @@ -43,7 +44,7 @@ public XmlMappingBinderAccess(ServiceRegistry serviceRegistry) { this.mappingBinder = new MappingBinder( serviceRegistry ); } - public XmlMappingBinderAccess(ServiceRegistry serviceRegistry, Function configAccess) { + public XmlMappingBinderAccess(ServiceRegistry serviceRegistry, Supplier> configAccess) { this.classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); this.mappingBinder = new MappingBinder( classLoaderService, configAccess ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/xsd/XmlValidationMode.java b/hibernate-core/src/main/java/org/hibernate/boot/xsd/XmlValidationMode.java new file mode 100644 index 000000000000..984954d88ec6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/xsd/XmlValidationMode.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.xsd; + + + +import java.util.Locale; + +/** + * Indicates the kind of validation that should be applied to xml mappings (hbm or orm.xml) + * + * @author Jan Schatteman + */ +public enum XmlValidationMode { + /* + * No validation of the xml file is done. + */ + DISABLED, + /* + * The xml file is validated against the latest version of the orm.xsd schema. + */ + STRICT, + /* + * The xml file is validated against the latest version of hibernate's own extension of the orm schema, i.e. the mapping.xsd schema. + * This is the default setting. + */ + EXTENDED; + + /** + * Resolve the parameter to a valid {@link XmlValidationMode} instance. + * + * @param xmlValidationMode the configured {@link XmlValidationMode}, as a case-insensitive String or a XmlValidationMode Object + * @return the corresponding {@link XmlValidationMode} object + */ + public static XmlValidationMode interpret(Object xmlValidationMode) { + if ( xmlValidationMode instanceof XmlValidationMode ) { + return (XmlValidationMode) xmlValidationMode; + } + + final String stringVal; + if ( xmlValidationMode == null || (stringVal = xmlValidationMode.toString()).isBlank() ) { + return DISABLED; + } + + return XmlValidationMode.valueOf( stringVal.toUpperCase( Locale.ROOT ) ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index 12d04dd1df71..e3aa0aee8b7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -52,6 +52,7 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.EmptyInterceptor; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.resource.jdbc.spi.StatementInspector; @@ -204,7 +205,7 @@ public Configuration(BootstrapServiceRegistry serviceRegistry) { private XmlMappingBinderAccess createMappingBinderAccess(BootstrapServiceRegistry serviceRegistry) { return new XmlMappingBinderAccess( serviceRegistry, - (settingName) -> properties == null ? null : properties.get( settingName ) + () -> properties == null ? null : CollectionHelper.asMap( properties ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java index ebb742465432..63d9f3a72a37 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/MappingSettings.java @@ -10,6 +10,7 @@ import org.hibernate.annotations.Nationalized; import org.hibernate.boot.jaxb.hbm.transform.UnsupportedFeatureHandling; import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.dialect.Dialect; import org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy; import org.hibernate.id.enhanced.StandardOptimizerDescriptor; @@ -480,12 +481,25 @@ public interface MappingSettings { /** * Whether XML should be validated against their schema as Hibernate reads them. * - * @settingDefault {@code true} + * @settingDefault {@code true}. * - * @since 6.1 + * @deprecated Instead use {@link org.hibernate.boot.xsd.XmlValidationMode#EXTENDED} for the default 'true' setting, or + * {@link org.hibernate.boot.xsd.XmlValidationMode#DISABLED} for false. */ + @Deprecated(since = "7.0", forRemoval = true) String VALIDATE_XML = "hibernate.validate_xml"; + /** + * Indicates what kind of validation should be applied to XML. + * + * @settingDefault {@link org.hibernate.boot.xsd.XmlValidationMode#DISABLED} + * @apiNote If present alongside the deprecated VALIDATE_XML setting, this setting's value prevails. + * + * @since 7.0 + * @see org.hibernate.boot.SessionFactoryBuilder#applyXmlValidationMode(XmlValidationMode) + */ + String XML_VALIDATION_MODE = "hibernate.xml_validation_mode"; + /** * Enables processing {@code hbm.xml} mappings by transforming them to {@code mapping.xml} and using * that processor. diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index f1dea4a5e78d..cfc77a68dc26 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -317,6 +317,14 @@ public static Properties asProperties(Map map) { } } + public static Map asMap(Properties props) { + final Map map = new HashMap<>(props.size()); + for (String key : props.stringPropertyNames()) { + map.put(key, props.getProperty(key)); + } + return map; + } + /** * Use to convert sets which will be retained for a long time, * such as for the lifetime of the Hibernate ORM instance. diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java index 6b032e3a45ab..b53367ed5f05 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java @@ -14,7 +14,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.hibernate.Incubating; +import org.hibernate.Internal; import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.xsd.XmlValidationMode; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -610,6 +612,21 @@ public static void setIfNotNull(Object value, String settingName, Map configurationSettings) { + // For backwards compatibility: whenever XML_VALIDATION_MODE is set it prevails over VALIDATE_XML, if not then + // VALIDATE_XML 'true' corresponds to XML_VALIDATION_MODE 'extended', whereas 'false' corresponds to 'disabled' + // If neither are set, the default is XmlValidationMode.DISABLED + final var validateXmlSetting = ConfigurationHelper.getBoolean( AvailableSettings.VALIDATE_XML, configurationSettings, false ); + final var xmlValidationModeSetting = configurationSettings.get( AvailableSettings.XML_VALIDATION_MODE ); + if (xmlValidationModeSetting == null) { + return validateXmlSetting ? XmlValidationMode.EXTENDED : XmlValidationMode.DISABLED; + } + else { + return XmlValidationMode.interpret( xmlValidationModeSetting ); + } + } + private static class TypeCodeConverter implements ConfigurationService.Converter { public static final TypeCodeConverter INSTANCE = new TypeCodeConverter(); diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceConfiguration.java b/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceConfiguration.java index 67e823efaab9..0047b00caa66 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/HibernatePersistenceConfiguration.java @@ -475,16 +475,6 @@ public HibernatePersistenceConfiguration xmlMappings(boolean enabled) { return this; } - /** - * Configures whether Hibernate should validate (via schema descriptor) XML files. - * - * @see MappingSettings#VALIDATE_XML - */ - public HibernatePersistenceConfiguration xmlValidation(boolean enabled) { - property( MappingSettings.VALIDATE_XML, enabled ); - return this; - } - /** * Configures whether Hibernate should collect {@linkplain org.hibernate.stat.Statistics}. * diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/PersistenceXmlParser.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/PersistenceXmlParser.java index 09e18f369ae3..0414af5bf632 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/PersistenceXmlParser.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/spi/PersistenceXmlParser.java @@ -14,9 +14,9 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import javax.xml.transform.stream.StreamSource; import org.hibernate.boot.archive.internal.ArchiveHelper; +import org.hibernate.boot.archive.internal.ByteArrayInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.configuration.spi.JaxbPersistenceImpl; @@ -342,10 +342,9 @@ private JaxbPersistenceImpl loadUrlWithJaxb(URL xmlUrl) { conn.setUseCaches( false ); try ( InputStream inputStream = conn.getInputStream() ) { - final StreamSource inputSource = new StreamSource( inputStream ); final ConfigurationBinder configurationBinder = new ConfigurationBinder( classLoaderService ); final Binding binding = - configurationBinder.bind( inputSource, new Origin( SourceType.URL, resourceName ) ); + configurationBinder.bind( new ByteArrayInputStreamAccess( resourceName, inputStream.readAllBytes() ), new Origin( SourceType.URL, resourceName ) ); return binding.getRoot(); } catch (IOException e) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/OrmXmlValidationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/OrmXmlValidationTest.java new file mode 100644 index 000000000000..468169578223 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/OrmXmlValidationTest.java @@ -0,0 +1,108 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.boot.jaxb.mapping; + + +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; +import org.hibernate.boot.jaxb.Origin; +import org.hibernate.boot.jaxb.SourceType; +import org.hibernate.boot.jaxb.internal.MappingBinder; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.ServiceRegistryScope; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.fail; + + +/** + * @author Jan Schatteman + */ +public class OrmXmlValidationTest { + + @Test + @ServiceRegistry + public void testValidatingOK_Hbm(ServiceRegistryScope scope) { + scope.withService( ClassLoaderService.class, + (cls) -> { + final String resourceName = "xml/jaxb/mapping/basic/hbm.xml"; + try (InputStream inputStream = cls.locateResourceStream( resourceName )) { + final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.DEFAULT_VALIDATING ); + mappingBinder.bind( + new RepeatableInputStreamAccess( resourceName, inputStream ), + new Origin( SourceType.RESOURCE, resourceName ) + ); + } + catch (Exception e) { + fail(); + } + } + ); + } + + @Test + @ServiceRegistry + public void testValidatingOK_Orm(ServiceRegistryScope scope) { + scope.withService( ClassLoaderService.class, + (cls) -> { + final String resourceName = "xml/jaxb/mapping/validation/orm.xml"; + try (InputStream inputStream = cls.locateResourceStream( resourceName )) { + final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.DEFAULT_VALIDATING ); + mappingBinder.bind( + new RepeatableInputStreamAccess( resourceName, inputStream ), + new Origin( SourceType.RESOURCE, resourceName ) + ); + } + catch (Exception e) { + fail(); + } + } + ); + } + + @Test + @ServiceRegistry + public void testStrictValidatingOK_Orm(ServiceRegistryScope scope) { + scope.withService( ClassLoaderService.class, + (cls) -> { + final String resourceName = "xml/jaxb/mapping/validation/orm.xml"; + try (InputStream inputStream = cls.locateResourceStream( resourceName )) { + final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.STRICT_VALIDATING ); + mappingBinder.bind( + new RepeatableInputStreamAccess( resourceName, inputStream ), + new Origin( SourceType.RESOURCE, resourceName ) + ); + } + catch (Exception e) { + fail(); + } + } + ); + } + + @Test + @ServiceRegistry + public void testStrictValidatingKO_Orm(ServiceRegistryScope scope) { + scope.withService( ClassLoaderService.class, + (cls) -> { + final String resourceName = "xml/jaxb/mapping/validation/orm-specific_attrib.xml"; + try ( InputStream inputStream = cls.locateResourceStream( resourceName ) ) { + final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.STRICT_VALIDATING ); + mappingBinder.bind( + new RepeatableInputStreamAccess( resourceName, inputStream ), + new Origin( SourceType.RESOURCE, resourceName ) + ); + fail(); + } + catch (Exception e) { + // Nothing to do + } + } + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/PartialJaxbTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/PartialJaxbTests.java index 6363f4d0822f..7ad3da2670b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/PartialJaxbTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/PartialJaxbTests.java @@ -4,6 +4,7 @@ */ package org.hibernate.orm.test.boot.jaxb.mapping; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -27,10 +28,11 @@ public class PartialJaxbTests { public void cachingTest(ServiceRegistryScope scope) { final MappingBinder mappingBinder = new MappingBinder( scope.getRegistry() ); scope.withService( ClassLoaderService.class, (cls) -> { + final String resourceLocation = "xml/jaxb/mapping/partial/caching.xml"; //noinspection unchecked final Binding binding = mappingBinder.bind( - cls.locateResourceStream( "xml/jaxb/mapping/partial/caching.xml" ), - new Origin( SourceType.RESOURCE, "xml/jaxb/mapping/partial/caching.xml" ) + new RepeatableInputStreamAccess( resourceLocation, cls.locateResourceStream( resourceLocation )), + new Origin( SourceType.RESOURCE, resourceLocation ) ); final JaxbEntityMappingsImpl entityMappings = binding.getRoot(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/XmlHelper.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/XmlHelper.java index 8b97c192bad2..2c58a20e64ae 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/XmlHelper.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/XmlHelper.java @@ -9,6 +9,7 @@ import java.net.URL; import org.hibernate.boot.ResourceStreamLocator; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -33,7 +34,7 @@ public static JaxbEntityMappingsImpl loadMapping(String resourceName, ClassLoadi final ResourceStreamLocatorImpl resourceStreamLocator = new ResourceStreamLocatorImpl( classLoadingAccess ); final MappingBinder mappingBinder = new MappingBinder( resourceStreamLocator, NON_VALIDATING ); final Binding binding = mappingBinder.bind( - resourceStreamLocator.locateResourceStream( resourceName ), + new RepeatableInputStreamAccess( resourceName, resourceStreamLocator.locateResourceStream( resourceName )), new Origin( SourceType.RESOURCE, resourceName ) ); return (JaxbEntityMappingsImpl) binding.getRoot(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/hbm/_extends/ExtendsTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/hbm/_extends/ExtendsTests.java index aac2b4798e8b..79c8f778e727 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/hbm/_extends/ExtendsTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/hbm/_extends/ExtendsTests.java @@ -5,6 +5,7 @@ package org.hibernate.orm.test.boot.models.hbm._extends; import jakarta.persistence.InheritanceType; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -39,7 +40,7 @@ void testDiscriminatedSeparated(ServiceRegistryScope registryScope) { try (InputStream stream = classLoaderService.locateResourceStream( mappingName )) { final Binding binding = mappingBinder.bind( - stream, + new RepeatableInputStreamAccess( SourceType.INPUT_STREAM.toString(), stream), new Origin( SourceType.RESOURCE, mappingName ) ); verifyHierarchy( (JaxbEntityMappingsImpl) binding.getRoot(), InheritanceType.SINGLE_TABLE ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/AttributeOverrideXmlTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/AttributeOverrideXmlTests.java index ac5c2fef7872..150a933f0b33 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/AttributeOverrideXmlTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/AttributeOverrideXmlTests.java @@ -38,7 +38,7 @@ public class AttributeOverrideXmlTests { void testBasicHandling(ServiceRegistryScope serviceRegistryScope) { final StandardServiceRegistry registry = serviceRegistryScope.getRegistry(); - final MetadataSources metadataSources = new MetadataSources().addResource( "org/hibernate/orm/test/jpa/xml/orm3.xml" ); + final MetadataSources metadataSources = new MetadataSources( registry ).addResource( "org/hibernate/orm/test/jpa/xml/orm3.xml" ); final MetadataBuildingOptionsImpl options = new MetadataBuildingOptionsImpl( registry ); final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl( registry, options ); options.setBootstrapContext( bootstrapContext ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/SimpleOverrideXmlTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/SimpleOverrideXmlTests.java index 9f9a154c5d86..786b9ff5e5b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/SimpleOverrideXmlTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/models/xml/override/SimpleOverrideXmlTests.java @@ -36,7 +36,7 @@ public class SimpleOverrideXmlTests { void testSimpleCompleteEntity(ServiceRegistryScope scope) { final StandardServiceRegistry serviceRegistry = scope.getRegistry(); - final MetadataSources metadataSources = new MetadataSources().addResource( "mappings/models/override/simple-override.xml" ); + final MetadataSources metadataSources = new MetadataSources( serviceRegistry ).addResource( "mappings/models/override/simple-override.xml" ); final MetadataBuilderImpl.MetadataBuildingOptionsImpl options = new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ); final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl( serviceRegistry, options ); options.setBootstrapContext( bootstrapContext ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/internal/util/xml/XMLMappingHelper.java b/hibernate-core/src/test/java/org/hibernate/orm/test/internal/util/xml/XMLMappingHelper.java index e8786df9a90c..0119f56e378e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/internal/util/xml/XMLMappingHelper.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/internal/util/xml/XMLMappingHelper.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.internal.MappingBinder; @@ -23,7 +24,7 @@ public final class XMLMappingHelper { private final MappingBinder binder; public XMLMappingHelper() { - binder = new MappingBinder( ClassLoaderServiceTestingImpl.INSTANCE, MappingBinder.VALIDATING ); + binder = new MappingBinder( ClassLoaderServiceTestingImpl.INSTANCE, MappingBinder.DEFAULT_VALIDATING ); } public JaxbEntityMappingsImpl readOrmXmlMappings(String name) throws IOException { @@ -35,7 +36,7 @@ public JaxbEntityMappingsImpl readOrmXmlMappings(String name) throws IOException public JaxbEntityMappingsImpl readOrmXmlMappings(InputStream is, String name) { try { Assert.assertNotNull( "Resource not found: " + name, is ); - Binding binding = binder.bind( is, new Origin( SourceType.JAR, name ) ); + Binding binding = binder.bind( new RepeatableInputStreamAccess( name, is), new Origin( SourceType.JAR, name ) ); return (JaxbEntityMappingsImpl) binding.getRoot(); } catch (RuntimeException e) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaXmlSmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaXmlSmokeTests.java index f2571d48c463..8980a34528c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaXmlSmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaXmlSmokeTests.java @@ -8,7 +8,6 @@ import java.io.InputStream; import java.util.Map; import java.util.stream.Stream; -import javax.xml.transform.stream.StreamSource; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; @@ -37,11 +36,11 @@ public class JakartaXmlSmokeTests { @Test public void testLoadingOrmXml(ServiceRegistryScope scope) { final ClassLoaderService cls = scope.getRegistry().getService( ClassLoaderService.class ); - final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.VALIDATING ); - final InputStream inputStream = cls.locateResourceStream( "xml/jakarta/simple/orm.xml" ); + final MappingBinder mappingBinder = new MappingBinder( cls, MappingBinder.DEFAULT_VALIDATING ); + final String resourceName = "xml/jakarta/simple/orm.xml"; + final InputStream inputStream = cls.locateResourceStream( resourceName ); try { - final Binding binding = mappingBinder.bind( new StreamSource( inputStream ), new Origin( SourceType.RESOURCE, "xml/jakarta/simple/orm.xml" ) ); - + final Binding binding = mappingBinder.bind( inputStream, new Origin( SourceType.RESOURCE, resourceName ) ); assertThat( binding.getRoot() .getEntities() .stream() diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/contributed/ContributorImpl.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/contributed/ContributorImpl.java index a74bc609f1bd..aabbb5c17c7b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/contributed/ContributorImpl.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/contributed/ContributorImpl.java @@ -7,6 +7,7 @@ import java.io.InputStream; import org.hibernate.boot.ResourceStreamLocator; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping; @@ -36,16 +37,17 @@ public void contribute( InFlightMetadataCollector metadata, ResourceStreamLocator resourceStreamLocator, MetadataBuildingContext buildingContext) { + final String resourceLocation = "org/hibernate/orm/test/mapping/contributed/BasicContributorTests.hbm.xml"; final Origin origin = new Origin( SourceType.OTHER, "test" ); final ClassLoaderService classLoaderService = buildingContext.getBootstrapContext() .getServiceRegistry() .getService( ClassLoaderService.class ); final InputStream inputStream = classLoaderService.locateResourceStream( - "org/hibernate/orm/test/mapping/contributed/BasicContributorTests.hbm.xml" ); + resourceLocation ); final MappingBinder mappingBinder = new MappingBinder( buildingContext.getBootstrapContext().getServiceRegistry() ); - final Binding jaxbBinding = mappingBinder.bind( inputStream, origin ); + final Binding jaxbBinding = mappingBinder.bind( new RepeatableInputStreamAccess( resourceLocation, inputStream), origin ); final JaxbHbmHibernateMapping jaxbRoot = jaxbBinding.getRoot(); contributions.contributeBinding( jaxbRoot ); diff --git a/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm-specific_attrib.xml b/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm-specific_attrib.xml new file mode 100644 index 000000000000..a239e4b5afc9 --- /dev/null +++ b/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm-specific_attrib.xml @@ -0,0 +1,19 @@ + + + + + org.hibernate.orm.test.boot.jaxb.mapping + + + + + + + + + \ No newline at end of file diff --git a/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm.xml b/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm.xml new file mode 100644 index 000000000000..159353e45e5c --- /dev/null +++ b/hibernate-core/src/test/resources/xml/jaxb/mapping/validation/orm.xml @@ -0,0 +1,17 @@ + + + + + org.hibernate.orm.test.boot.jaxb.mapping + + + + + + + \ No newline at end of file diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/misc/TransformHbmXmlTask.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/misc/TransformHbmXmlTask.java index bd8e9c51aa15..22d1714cde62 100644 --- a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/misc/TransformHbmXmlTask.java +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/misc/TransformHbmXmlTask.java @@ -21,6 +21,7 @@ import org.gradle.api.tasks.TaskAction; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping; import org.hibernate.boot.jaxb.hbm.transform.HbmXmlTransformer; @@ -188,7 +189,7 @@ private void performTransformation( private Binding bindMapping(MappingBinder mappingBinder, File hbmXmlFile, Origin origin) { try ( final FileInputStream fileStream = new FileInputStream( hbmXmlFile ) ) { - return mappingBinder.bind( fileStream, origin ); + return mappingBinder.bind( new RepeatableInputStreamAccess( hbmXmlFile.getPath(), fileStream), origin ); } catch (IOException e) { getProject().getLogger() diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/JpaDescriptorParser.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/JpaDescriptorParser.java index 7a24e37c2551..266f01d06cb1 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/JpaDescriptorParser.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/xml/JpaDescriptorParser.java @@ -17,12 +17,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.function.Function; +import java.util.Map; +import java.util.function.Supplier; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.tools.Diagnostic; import org.hibernate.MappingException; +import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.jaxb.configuration.spi.JaxbPersistenceImpl; @@ -70,7 +72,7 @@ public JpaDescriptorParser(Context context) { final ResourceStreamLocatorImpl resourceStreamLocator = new ResourceStreamLocatorImpl( context ); this.configurationBinder = new ConfigurationBinder( resourceStreamLocator ); - this.mappingBinder = new MappingBinder( resourceStreamLocator, (Function) null ); + this.mappingBinder = new MappingBinder( resourceStreamLocator, (Supplier>) null ); } public void parseMappingXml() { @@ -129,7 +131,7 @@ private Collection determineMappingFileNames() { try { final Binding binding = configurationBinder.bind( - stream, + new RepeatableInputStreamAccess( persistenceXmlLocation, stream ), new Origin( SourceType.RESOURCE, persistenceXmlLocation ) ); persistence = binding.getRoot(); @@ -165,7 +167,7 @@ private void loadEntityMappings(Collection mappingFileNames) { } try { final Binding binding = mappingBinder.bind( - inputStream, + new RepeatableInputStreamAccess( mappingFile, inputStream), new Origin( SourceType.RESOURCE, mappingFile ) ); if ( binding != null ) {