From 27370d3215e96d3be720a30813e45c461d0af325 Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Tue, 14 Mar 2017 11:57:04 +0100 Subject: [PATCH] HV-1280 Setting context class loader to HV's defining CL before calling into JAXB/JAXP; This makes sure we don't get in touch with a JAXP implementation provided as part of the deployed application when running on WF. --- .../internal/xml/ValidationXmlParser.java | 5 + .../internal/xml/XmlMappingParser.java | 31 ++-- .../integration/wildfly/xml/Camera.java | 15 ++ .../xml/JaxpContainedInDeploymentIT.java | 142 ++++++++++++++++++ 4 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/Camera.java create mode 100644 integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/JaxpContainedInDeploymentIT.java diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/ValidationXmlParser.java b/engine/src/main/java/org/hibernate/validator/internal/xml/ValidationXmlParser.java index c93371e9c0..35cf241e3f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/ValidationXmlParser.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/ValidationXmlParser.java @@ -69,7 +69,11 @@ public final BootstrapConfiguration parseValidationXml() { return BootstrapConfigurationImpl.getDefaultBootstrapConfiguration(); } + ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader( ValidationXmlParser.class.getClassLoader() ); + // HV-970 The parser helper is only loaded if there actually is a validation.xml file; // this avoids accessing javax.xml.stream.* (which does not exist on Android) when not actually // working with the XML configuration @@ -83,6 +87,7 @@ public final BootstrapConfiguration parseValidationXml() { return createBootstrapConfiguration( validationConfig ); } finally { + Thread.currentThread().setContextClassLoader( previousTccl ); closeStream( inputStream ); } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/XmlMappingParser.java b/engine/src/main/java/org/hibernate/validator/internal/xml/XmlMappingParser.java index 0ed463ab50..dd753eda7c 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/XmlMappingParser.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/XmlMappingParser.java @@ -137,15 +137,7 @@ public final void parse(Set mappingStreams) { in.mark( Integer.MAX_VALUE ); } - XMLEventReader xmlEventReader = xmlParserHelper.createXmlEventReader( "constraint mapping file", new CloseIgnoringInputStream( in ) ); - String schemaVersion = xmlParserHelper.getSchemaVersion( "constraint mapping file", xmlEventReader ); - String schemaResourceName = getSchemaResourceName( schemaVersion ); - Schema schema = xmlParserHelper.getSchema( schemaResourceName ); - - Unmarshaller unmarshaller = jc.createUnmarshaller(); - unmarshaller.setSchema( schema ); - - ConstraintMappingsType mapping = getValidationConfig( xmlEventReader, unmarshaller ); + ConstraintMappingsType mapping = unmarshal( jc, in ); String defaultPackage = mapping.getDefaultPackage(); parseConstraintDefinitions( @@ -180,6 +172,27 @@ public final void parse(Set mappingStreams) { } } + private ConstraintMappingsType unmarshal(JAXBContext jc, InputStream in) throws JAXBException { + ClassLoader previousTccl = Thread.currentThread().getContextClassLoader(); + + try { + Thread.currentThread().setContextClassLoader( ValidationXmlParser.class.getClassLoader() ); + + XMLEventReader xmlEventReader = xmlParserHelper.createXmlEventReader( "constraint mapping file", new CloseIgnoringInputStream( in ) ); + String schemaVersion = xmlParserHelper.getSchemaVersion( "constraint mapping file", xmlEventReader ); + String schemaResourceName = getSchemaResourceName( schemaVersion ); + Schema schema = xmlParserHelper.getSchema( schemaResourceName ); + + Unmarshaller unmarshaller = jc.createUnmarshaller(); + unmarshaller.setSchema( schema ); + + return getValidationConfig( xmlEventReader, unmarshaller ); + } + finally { + Thread.currentThread().setContextClassLoader( previousTccl ); + } + } + public final Set> getXmlConfiguredClasses() { return processedClasses; } diff --git a/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/Camera.java b/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/Camera.java new file mode 100644 index 0000000000..6ffdb34fca --- /dev/null +++ b/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/Camera.java @@ -0,0 +1,15 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.integration.wildfly.xml; + +/** + * @author Gunnar Morling + */ +public class Camera { + + public String brand = null; +} diff --git a/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/JaxpContainedInDeploymentIT.java b/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/JaxpContainedInDeploymentIT.java new file mode 100644 index 0000000000..deb21cbd6b --- /dev/null +++ b/integration/src/test/java/org/hibernate/validator/integration/wildfly/xml/JaxpContainedInDeploymentIT.java @@ -0,0 +1,142 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.integration.wildfly.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.Set; + +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import javax.validation.constraints.NotNull; + +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.descriptor.api.Descriptors; +import org.jboss.shrinkwrap.descriptor.api.validationConfiguration11.ValidationConfigurationDescriptor; +import org.jboss.shrinkwrap.descriptor.api.validationMapping11.ValidationMappingDescriptor; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for https://hibernate.atlassian.net/browse/HV-1280. To reproduce the issue, the deployment must be done twice + * (it will only show up during the 2nd deploy), which is why the test is managing the deployment itself via client-side + * test methods. + * + * @author Gunnar Morling + */ +@RunWith(Arquillian.class) +public class JaxpContainedInDeploymentIT { + + private static final String WAR_FILE_NAME = JaxpContainedInDeploymentIT.class.getSimpleName() + ".war"; + + @ArquillianResource + private Deployer deployer; + + @Inject + private Validator validator; + + @Deployment(name = "jaxpit", managed = false) + public static Archive createTestArchive() { + return ShrinkWrap + .create( WebArchive.class, WAR_FILE_NAME ) + .addClass( Camera.class ) + .addAsResource( validationXml(), "META-INF/validation.xml" ) + .addAsResource( mappingXml(), "META-INF/my-mapping.xml" ) + .addAsLibrary( Maven.resolver().resolve( "xerces:xercesImpl:2.9.1" ).withoutTransitivity().asSingleFile() ) + .addAsResource( "log4j.properties" ) + .addAsWebInfResource( EmptyAsset.INSTANCE, "beans.xml" ); + } + + private static Asset validationXml() { + String validationXml = Descriptors.create( ValidationConfigurationDescriptor.class ) + .version( "1.1" ) + .constraintMapping( "META-INF/my-mapping.xml" ) + .exportAsString(); + return new StringAsset( validationXml ); + } + + private static Asset mappingXml() { + String mappingXml = Descriptors.create( ValidationMappingDescriptor.class ) + .version( "1.1" ) + .createBean() + .clazz( Camera.class.getName() ) + .createField() + .name( "brand" ) + .createConstraint() + .annotation( "javax.validation.constraints.NotNull" ) + .up() + .up() + .up() + .exportAsString(); + return new StringAsset( mappingXml ); + } + + @Test + @RunAsClient + @InSequence(0) + public void deploy1() throws Exception { + deployer.deploy( "jaxpit" ); + } + + @Test + @InSequence(1) + public void test1() throws Exception { + doTest(); + } + + @Test + @RunAsClient + @InSequence(2) + public void undeploy1() throws Exception { + deployer.undeploy( "jaxpit" ); + } + + @Test + @RunAsClient + @InSequence(3) + public void deploy2() throws Exception { + deployer.deploy( "jaxpit" ); + } + + @Test + @InSequence(4) + public void test2() throws Exception { + doTest(); + } + + @Test + @RunAsClient + @InSequence(5) + public void undeploy2() throws Exception { + deployer.undeploy( "jaxpit" ); + } + + private void doTest() { + Set> violations = validator.validate( new Camera() ); + + assertEquals( 1, violations.size() ); + assertSame( NotNull.class, violations.iterator() + .next() + .getConstraintDescriptor() + .getAnnotation() + .annotationType() ); + } +}