Skip to content

Commit 18e4484

Browse files
committed
HHH-18455 - Change Binder.bind(InputStream, Origin) method signature to bind(InputStreamAccess, Origin) to allow repeatable access to the InputStream, needed for strict Jpa XML validation
HHH-18455 - Implement option to run strict JPA compliance validation Signed-off-by: Jan Schatteman <[email protected]>
1 parent 97be9ba commit 18e4484

35 files changed

+519
-99
lines changed

hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
2323
import org.hibernate.resource.jdbc.spi.StatementInspector;
2424
import org.hibernate.type.format.FormatMapper;
25+
import org.hibernate.boot.xsd.XmlValidationMode;
2526

2627
import jakarta.persistence.criteria.Nulls;
2728

@@ -755,6 +756,18 @@ public interface SessionFactoryBuilder {
755756
@Incubating
756757
SessionFactoryBuilder applyXmlFormatMapper(FormatMapper xmlFormatMapper);
757758

759+
/**
760+
* Specifies a {@link XmlValidationMode validation mode} to use for validation of XML files.
761+
*
762+
* @param xmlValidationMode The {@link XmlValidationMode} to use.
763+
*
764+
* @return {@code this}, for method chaining
765+
*
766+
* @see org.hibernate.cfg.AvailableSettings#XML_VALIDATION_MODE
767+
*/
768+
@Incubating
769+
SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode);
770+
758771
/**
759772
* After all options have been set, build the SessionFactory.
760773
*
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.archive.internal;
6+
7+
8+
import org.hibernate.HibernateException;
9+
import org.hibernate.boot.archive.spi.InputStreamAccess;
10+
11+
import java.io.ByteArrayInputStream;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
15+
/**
16+
* @author Jan Schatteman
17+
*/
18+
public class RepeatableInputStreamAccess implements InputStreamAccess {
19+
20+
private final String resourceName;
21+
private byte[] bytes = new byte[0];
22+
23+
public RepeatableInputStreamAccess(String resourceName, InputStream inputStream) {
24+
if ( inputStream == null ) {
25+
throw new HibernateException( "InputStream is null for resource " + resourceName );
26+
}
27+
this.resourceName = resourceName;
28+
try {
29+
bytes = inputStream.readAllBytes();
30+
}
31+
catch (IOException | OutOfMemoryError e) {
32+
throw new HibernateException( "Could not read resource " + resourceName, e );
33+
}
34+
}
35+
36+
@Override
37+
public String getStreamName() {
38+
return resourceName;
39+
}
40+
41+
@Override
42+
public InputStream accessInputStream() {
43+
return new ByteArrayInputStream( bytes );
44+
}
45+
46+
}

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.hibernate.boot.spi.MetadataImplementor;
1919
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
2020
import org.hibernate.boot.spi.SessionFactoryOptions;
21+
import org.hibernate.boot.xsd.XmlValidationMode;
2122
import org.hibernate.bytecode.internal.SessionFactoryObserverForBytecodeEnhancer;
2223
import org.hibernate.bytecode.spi.BytecodeProvider;
2324
import org.hibernate.cache.spi.TimestampsCacheFactory;
@@ -431,6 +432,12 @@ public void disableJtaTransactionAccess() {
431432
this.optionsBuilder.disableJtaTransactionAccess();
432433
}
433434

435+
@Override
436+
public SessionFactoryBuilder applyXmlValidationMode(XmlValidationMode xmlValidationMode) {
437+
this.optionsBuilder.applyXmlValidationMode( xmlValidationMode );
438+
return this;
439+
}
440+
434441
@Override
435442
public SessionFactory build() {
436443
return new SessionFactoryImpl( metadata, buildSessionFactoryOptions(), bootstrapContext );

hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.hibernate.boot.spi.BootstrapContext;
4343
import org.hibernate.boot.spi.MetadataBuildingContext;
4444
import org.hibernate.boot.spi.SessionFactoryOptions;
45+
import org.hibernate.boot.xsd.XmlValidationMode;
4546
import org.hibernate.cache.internal.NoCachingRegionFactory;
4647
import org.hibernate.cache.internal.StandardTimestampsCacheFactory;
4748
import org.hibernate.cache.spi.RegionFactory;
@@ -240,6 +241,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
240241

241242
private final int queryStatisticsMaxSize;
242243

244+
private XmlValidationMode xmlValidationMode;
243245
private final Map<String, Object> defaultSessionProperties;
244246
private final CacheStoreMode defaultCacheStoreMode;
245247
private final CacheRetrieveMode defaultCacheRetrieveMode;
@@ -538,6 +540,8 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo
538540

539541
defaultLockOptions = defaultLockOptions( defaultSessionProperties );
540542
initialSessionFlushMode = defaultFlushMode( defaultSessionProperties );
543+
544+
xmlValidationMode = ConfigurationHelper.resolveXmlValidationMode( settings );
541545
}
542546

543547
@Deprecated(forRemoval = true)
@@ -1409,6 +1413,9 @@ public boolean isPreferJdbcDatetimeTypesInNativeQueriesEnabled() {
14091413
return preferJdbcDatetimeTypes;
14101414
}
14111415

1416+
@Override
1417+
public XmlValidationMode getXmlValidationMode() { return xmlValidationMode; }
1418+
14121419
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14131420
// In-flight mutation access
14141421

@@ -1686,7 +1693,6 @@ public void enableGeneratorNameScopeCompliance(boolean enabled) {
16861693
mutableJpaCompliance().setGeneratorNameScopeCompliance( enabled );
16871694
}
16881695

1689-
16901696
public void enableCollectionInDefaultFetchGroup(boolean enabled) {
16911697
this.collectionsInDefaultFetchGroupEnabled = enabled;
16921698
}
@@ -1695,6 +1701,10 @@ public void disableJtaTransactionAccess() {
16951701
this.jtaTransactionAccessEnabled = false;
16961702
}
16971703

1704+
public void applyXmlValidationMode(XmlValidationMode xmlValidationMode) {
1705+
this.xmlValidationMode = xmlValidationMode;
1706+
}
1707+
16981708
public SessionFactoryOptions buildOptions() {
16991709
if ( jpaCompliance instanceof MutableJpaCompliance mutableJpaCompliance ) {
17001710
jpaCompliance = mutableJpaCompliance.immutableCopy();

hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/AbstractBinder.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@
55
package org.hibernate.boot.jaxb.internal;
66

77
import java.io.InputStream;
8+
import java.util.function.Consumer;
89
import javax.xml.stream.XMLEventReader;
910
import javax.xml.stream.XMLInputFactory;
1011
import javax.xml.stream.XMLStreamException;
1112
import javax.xml.stream.events.StartElement;
1213
import javax.xml.stream.events.XMLEvent;
1314
import javax.xml.transform.Source;
14-
import javax.xml.validation.Schema;
1515

1616
import org.hibernate.boot.MappingException;
1717
import org.hibernate.boot.ResourceStreamLocator;
18+
import org.hibernate.boot.archive.spi.InputStreamAccess;
1819
import org.hibernate.boot.jaxb.Origin;
1920
import org.hibernate.boot.jaxb.internal.stax.BufferedXMLEventReader;
2021
import org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver;
2122
import org.hibernate.boot.jaxb.spi.Binder;
2223
import org.hibernate.boot.jaxb.spi.Binding;
24+
import org.hibernate.boot.xsd.XmlValidationMode;
2325
import org.hibernate.internal.util.StringHelper;
2426

2527
import org.jboss.logging.Logger;
@@ -36,15 +38,18 @@ public abstract class AbstractBinder<T> implements Binder<T> {
3638

3739
private final LocalXmlResourceResolver xmlResourceResolver;
3840

41+
protected InputStreamAccess streamAccess;
42+
3943
protected AbstractBinder(ResourceStreamLocator resourceStreamLocator) {
4044
this.xmlResourceResolver = new LocalXmlResourceResolver( resourceStreamLocator );
4145
}
4246

43-
public abstract boolean isValidationEnabled();
47+
public abstract XmlValidationMode getXmlValidationMode();
4448

4549
@Override
46-
public <X extends T> Binding<X> bind(InputStream stream, Origin origin) {
47-
final XMLEventReader eventReader = createReader( stream, origin );
50+
public <X extends T> Binding<X> bind(InputStreamAccess streamAccess, Origin origin) {
51+
this.streamAccess = streamAccess;
52+
final XMLEventReader eventReader = createReader( streamAccess.accessInputStream(), origin );
4853
try {
4954
return doBind( eventReader, origin );
5055
}
@@ -145,17 +150,19 @@ protected static boolean hasNamespace(StartElement startElement) {
145150
return StringHelper.isNotEmpty( startElement.getName().getNamespaceURI() );
146151
}
147152

148-
protected <X extends T> X jaxb(XMLEventReader reader, Schema xsd, JAXBContext jaxbContext, Origin origin) {
153+
protected void validateXml(Unmarshaller unmarshaller, Consumer<Unmarshaller> validationAction) {
154+
// handled by subclasses if needed/applicable
155+
validationAction.accept(unmarshaller);
156+
}
157+
158+
protected <X extends T> X jaxb(XMLEventReader reader, JAXBContext jaxbContext, Origin origin, Consumer<Unmarshaller> validationAction) {
149159
final ContextProvidingValidationEventHandler handler = new ContextProvidingValidationEventHandler();
150160

151161
try {
152162
final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
153-
if ( isValidationEnabled() ) {
154-
unmarshaller.setSchema( xsd );
155-
}
156-
else {
157-
unmarshaller.setSchema( null );
158-
}
163+
164+
validateXml( unmarshaller, validationAction );
165+
159166
unmarshaller.setEventHandler( handler );
160167

161168
//noinspection unchecked

hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/ConfigurationBinder.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@
88
import javax.xml.stream.XMLEventReader;
99
import javax.xml.stream.events.StartElement;
1010

11+
import jakarta.xml.bind.Unmarshaller;
1112
import org.hibernate.Internal;
1213
import org.hibernate.boot.ResourceStreamLocator;
1314
import org.hibernate.boot.jaxb.Origin;
1415
import org.hibernate.boot.jaxb.configuration.spi.JaxbPersistenceImpl;
1516
import org.hibernate.boot.jaxb.internal.stax.ConfigurationEventReader;
1617
import org.hibernate.boot.jaxb.spi.Binding;
1718
import org.hibernate.boot.xsd.ConfigXsdSupport;
19+
import org.hibernate.boot.xsd.XmlValidationMode;
1820
import org.hibernate.internal.util.config.ConfigurationException;
1921

2022
import jakarta.xml.bind.JAXBContext;
2123
import jakarta.xml.bind.JAXBException;
2224

25+
import java.util.function.Consumer;
26+
2327
/**
2428
* @author Steve Ebersole
2529
*/
@@ -32,8 +36,8 @@ public ConfigurationBinder(ResourceStreamLocator resourceStreamLocator) {
3236
}
3337

3438
@Override
35-
public boolean isValidationEnabled() {
36-
return false;
39+
public XmlValidationMode getXmlValidationMode() {
40+
return XmlValidationMode.DISABLED;
3741
}
3842

3943
@Override
@@ -42,11 +46,22 @@ protected <X extends JaxbPersistenceImpl> Binding<X> doBind(
4246
StartElement rootElementStartEvent,
4347
Origin origin) {
4448
final XMLEventReader reader = new ConfigurationEventReader( staxEventReader, xmlEventFactory );
49+
50+
final Consumer<Unmarshaller> validationAction;
51+
// evaluate extended (the former validate_xml 'true') in case anyone should override getXmlValidationMode() to switch it on
52+
if ( getXmlValidationMode() == XmlValidationMode.EXTENDED ) {
53+
validationAction = unmarshaller -> unmarshaller.setSchema(
54+
ConfigXsdSupport.configurationXsd().getSchema() );
55+
}
56+
else {
57+
validationAction = unmarshaller -> unmarshaller.setSchema( null );
58+
}
59+
4560
final JaxbPersistenceImpl bindingRoot = jaxb(
4661
reader,
47-
ConfigXsdSupport.configurationXsd().getSchema(),
4862
jaxbContext(),
49-
origin
63+
origin,
64+
validationAction
5065
);
5166
//noinspection unchecked
5267
return new Binding<>( (X) bindingRoot, origin );

hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamAccessXmlSource.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public Binding doBind(Binder binder) {
2727
}
2828

2929
public static Binding doBind(Binder binder, InputStreamAccess inputStreamAccess, Origin origin) {
30-
return inputStreamAccess.fromStream(
31-
inputStream -> binder.bind( inputStream, origin )
32-
);
30+
return binder.bind( inputStreamAccess, origin ) ;
3331
}
3432
}

hibernate-core/src/main/java/org/hibernate/boot/jaxb/internal/InputStreamXmlSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.InputStream;
99

1010
import org.hibernate.boot.InvalidMappingException;
11+
import org.hibernate.boot.archive.internal.RepeatableInputStreamAccess;
1112
import org.hibernate.boot.jaxb.Origin;
1213
import org.hibernate.boot.jaxb.spi.Binder;
1314
import org.hibernate.boot.jaxb.spi.Binding;
@@ -38,7 +39,7 @@ public <T> Binding<T> doBind(Binder<T> binder) {
3839

3940
public static <T> Binding<T> doBind(Binder<T> binder, InputStream inputStream, Origin origin, boolean autoClose) {
4041
try {
41-
return binder.bind( inputStream, origin );
42+
return binder.bind( new RepeatableInputStreamAccess( origin.getName(), inputStream), origin );
4243
}
4344
catch ( Exception e ) {
4445
throw new InvalidMappingException( origin, e );

0 commit comments

Comments
 (0)