From 694b5ecaadb4f09df765a201b99ae3e382358e7c Mon Sep 17 00:00:00 2001
From: Sanne Grinovero <sanne@hibernate.org>
Date: Fri, 3 Jul 2020 13:01:18 +0100
Subject: [PATCH] HHH-14096 Adapt the Hibernate Envers module to use its
 private copy of XMLHelper

---
 .../envers/boot/internal/EnversService.java   |  3 -
 .../boot/internal/EnversServiceImpl.java      |  9 ---
 .../internal/RevisionInfoConfiguration.java   | 13 ++-
 .../configuration/internal/XMLHelper.java     | 79 +++++++++++++++++++
 4 files changed, 90 insertions(+), 14 deletions(-)
 create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java

diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java
index 55ba23e1bed..bdac75af98e 100644
--- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java
@@ -17,7 +17,6 @@
 import org.hibernate.envers.internal.revisioninfo.RevisionInfoQueryCreator;
 import org.hibernate.envers.internal.synchronization.AuditProcessManager;
 import org.hibernate.envers.strategy.AuditStrategy;
-import org.hibernate.internal.util.xml.XMLHelper;
 import org.hibernate.service.Service;
 import org.hibernate.service.ServiceRegistry;
 
@@ -56,8 +55,6 @@ public interface EnversService extends Service {
 
 	void initialize(MetadataImplementor metadata, MappingCollector mappingCollector);
 
-	XMLHelper getXmlHelper();
-
 	GlobalConfiguration getGlobalConfiguration();
 
 	AuditEntitiesConfiguration getAuditEntitiesConfiguration();
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java
index 7a9cf02bd8f..8f68358a81f 100644
--- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java
@@ -30,7 +30,6 @@
 import org.hibernate.envers.strategy.AuditStrategy;
 import org.hibernate.internal.util.ReflectHelper;
 import org.hibernate.internal.util.config.ConfigurationHelper;
-import org.hibernate.internal.util.xml.XMLHelper;
 import org.hibernate.service.ServiceRegistry;
 import org.hibernate.service.spi.Configurable;
 import org.hibernate.service.spi.Stoppable;
@@ -74,8 +73,6 @@ public class EnversServiceImpl implements EnversService, Configurable, Stoppable
 	private RevisionInfoNumberReader revisionInfoNumberReader;
 	private ModifiedEntityNamesReader modifiedEntityNamesReader;
 
-	private XMLHelper xmlHelper;
-
 	@Override
 	public void configure(Map configurationValues) {
 		if ( configurationValues.containsKey( LEGACY_AUTO_REGISTER ) ) {
@@ -111,7 +108,6 @@ public void initialize(final MetadataImplementor metadata, final MappingCollecto
 
 		this.serviceRegistry = metadata.getMetadataBuildingOptions().getServiceRegistry();
 		this.classLoaderService = serviceRegistry.getService( ClassLoaderService.class );
-		this.xmlHelper = new XMLHelper();
 
 		doInitialize( metadata, mappingCollector, serviceRegistry );
 	}
@@ -186,11 +182,6 @@ private static AuditStrategy initializeAuditStrategy(
 		return strategy;
 	}
 
-	@Override
-	public XMLHelper getXmlHelper() {
-		return xmlHelper;
-	}
-
 	/**
 	 * Load a class by name, preferring our ClassLoader and then the ClassLoaderService.
 	 *
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java
index e8b089ec298..97a7c0c35d1 100644
--- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java
@@ -55,6 +55,8 @@ public class RevisionInfoConfiguration {
 	private Type revisionInfoTimestampType;
 	private GlobalConfiguration globalCfg;
 
+	private XMLHelper xmlHelper;
+
 	private String revisionPropType;
 	private String revisionPropSqlType;
 
@@ -75,7 +77,7 @@ public RevisionInfoConfiguration(GlobalConfiguration globalCfg) {
 	}
 
 	private Document generateDefaultRevisionInfoXmlMapping() {
-		final Document document = globalCfg.getEnversService().getXmlHelper().getDocumentFactory().createDocument();
+		final Document document = getXmlHelper().getDocumentFactory().createDocument();
 
 		final Element classMapping = MetadataTools.createEntity(
 				document,
@@ -120,6 +122,13 @@ private Document generateDefaultRevisionInfoXmlMapping() {
 		return document;
 	}
 
+	private XMLHelper getXmlHelper() {
+		if ( this.xmlHelper == null ) {
+			this.xmlHelper = new XMLHelper();
+		}
+		return this.xmlHelper;
+	}
+
 	/**
 	 * Generates mapping that represents a set of primitive types.<br />
 	 * <code>
@@ -158,7 +167,7 @@ private void generateEntityNamesTrackingTableMapping(
 	}
 
 	private Element generateRevisionInfoRelationMapping() {
-		final Document document = globalCfg.getEnversService().getXmlHelper().getDocumentFactory().createDocument();
+		final Document document = getXmlHelper().getDocumentFactory().createDocument();
 		final Element revRelMapping = document.addElement( "key-many-to-one" );
 		revRelMapping.addAttribute( "type", revisionPropType );
 		revRelMapping.addAttribute( "class", revisionInfoEntityName );
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java
new file mode 100644
index 00000000000..655d73602de
--- /dev/null
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
+ */
+package org.hibernate.envers.configuration.internal;
+
+import org.dom4j.DocumentFactory;
+import org.dom4j.io.SAXReader;
+import org.hibernate.internal.util.xml.ErrorLogger;
+import org.xml.sax.EntityResolver;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Small helper class that lazily loads DOM and SAX reader and keep them for fast use afterwards.
+ *
+ * This was part of Hibernate ORM core, but moved into the testsuite helpers to not expose
+ * access to the dom4j types. It's also used by Hibernate Envers, so we will need two copies
+ * until Envers is able to remove its reliance on dom4j.
+ * The rest of Hibernate uses StAX now for XML processing.  See {@link org.hibernate.boot.jaxb.internal.stax}
+ */
+public final class XMLHelper {
+	private final DocumentFactory documentFactory;
+
+	public XMLHelper() {
+		PrivilegedAction<DocumentFactory> action = new PrivilegedAction<DocumentFactory>() {
+			public DocumentFactory run() {
+				final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader();
+				try {
+					// We need to make sure we get DocumentFactory
+					// loaded from the same ClassLoader that loads
+					// Hibernate classes, to make sure we get the
+					// proper version of DocumentFactory, This class
+					// is "internal", and should only be used for XML
+					// files generated by Envers.
+
+					// Using the (Hibernate) ClassLoader that loads
+					// this Class will avoid collisions in the case
+					// that DocumentFactory can be loaded from,
+					// for example, the application ClassLoader.
+					Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
+					return DocumentFactory.getInstance();
+				}
+				finally {
+					Thread.currentThread().setContextClassLoader( originalTccl );
+				}
+			}
+		};
+
+		this.documentFactory = System.getSecurityManager() != null
+				? AccessController.doPrivileged( action )
+				: action.run();
+	}
+
+	public DocumentFactory getDocumentFactory() {
+		return documentFactory;
+	}
+
+	public SAXReader createSAXReader(ErrorLogger errorLogger, EntityResolver entityResolver) {
+		SAXReader saxReader = new SAXReader();
+		try {
+			saxReader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
+			saxReader.setFeature( "http://xml.org/sax/features/external-general-entities", false );
+			saxReader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false );
+		}
+		catch (Exception e) {
+			throw new RuntimeException( e );
+		}
+		saxReader.setMergeAdjacentText( true );
+		saxReader.setValidation( true );
+		saxReader.setErrorHandler( errorLogger );
+		saxReader.setEntityResolver( entityResolver );
+
+		return saxReader;
+	}
+}