Skip to content

Commit

Permalink
Avoid creation of SAXParserFactory for every read operation
Browse files Browse the repository at this point in the history
Includes JAXBContext locking revision (avoiding synchronization) and consistent treatment of DocumentBuilderFactory (in terms of caching as well as locking).

Closes gh-32851
  • Loading branch information
jhoeller committed May 21, 2024
1 parent f26483d commit a4c2f29
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.xml.XMLConstants;
import javax.xml.datatype.Duration;
Expand Down Expand Up @@ -192,7 +194,7 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi
@Nullable
private ClassLoader beanClassLoader;

private final Object jaxbContextMonitor = new Object();
private final Lock jaxbContextLock = new ReentrantLock();

@Nullable
private volatile JAXBContext jaxbContext;
Expand All @@ -204,6 +206,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi

private boolean processExternalEntities = false;

@Nullable
private volatile SAXParserFactory schemaParserFactory;

@Nullable
private volatile SAXParserFactory sourceParserFactory;


/**
* Set multiple JAXB context paths. The given array of context paths gets
Expand Down Expand Up @@ -426,6 +434,7 @@ public void setMappedClass(Class<?> mappedClass) {
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.sourceParserFactory = null;
}

/**
Expand All @@ -450,6 +459,7 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
if (processExternalEntities) {
this.supportDtd = true;
}
this.sourceParserFactory = null;
}

/**
Expand Down Expand Up @@ -497,7 +507,9 @@ public JAXBContext getJaxbContext() {
if (context != null) {
return context;
}
synchronized (this.jaxbContextMonitor) {

this.jaxbContextLock.lock();
try {
context = this.jaxbContext;
if (context == null) {
try {
Expand All @@ -521,6 +533,9 @@ else if (!ObjectUtils.isEmpty(this.packagesToScan)) {
}
return context;
}
finally {
this.jaxbContextLock.unlock();
}
}

private JAXBContext createJaxbContextFromContextPath(String contextPath) throws JAXBException {
Expand Down Expand Up @@ -587,17 +602,24 @@ private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IO
Assert.notEmpty(resources, "No resources given");
Assert.hasLength(schemaLanguage, "No schema language provided");
Source[] schemaSources = new Source[resources.length];
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);

SAXParserFactory saxParserFactory = this.schemaParserFactory;
if (saxParserFactory == null) {
saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
this.schemaParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();

for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
Assert.isTrue(resource != null && resource.exists(), () -> "Resource does not exist: " + resource);
InputSource inputSource = SaxResourceUtils.createInputSource(resource);
schemaSources[i] = new SAXSource(xmlReader, inputSource);
}

SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
if (this.schemaResourceResolver != null) {
schemaFactory.setResourceResolver(this.schemaResourceResolver);
Expand Down Expand Up @@ -886,11 +908,16 @@ else if (streamSource.getReader() != null) {

try {
if (xmlReader == null) {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
String name = "http://xml.org/sax/features/external-general-entities";
saxParserFactory.setFeature(name, isProcessExternalEntities());
SAXParserFactory saxParserFactory = this.sourceParserFactory;
if (saxParserFactory == null) {
saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
saxParserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.sourceParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser();
xmlReader = saxParser.getXMLReader();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -83,9 +83,10 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
private boolean processExternalEntities = false;

@Nullable
private DocumentBuilderFactory documentBuilderFactory;
private volatile DocumentBuilderFactory documentBuilderFactory;

private final Object documentBuilderFactoryMonitor = new Object();
@Nullable
private volatile SAXParserFactory saxParserFactory;


/**
Expand All @@ -94,6 +95,8 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller {
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.documentBuilderFactory = null;
this.saxParserFactory = null;
}

/**
Expand All @@ -118,6 +121,8 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
if (processExternalEntities) {
this.supportDtd = true;
}
this.documentBuilderFactory = null;
this.saxParserFactory = null;
}

/**
Expand All @@ -137,14 +142,13 @@ public boolean isProcessExternalEntities() {
*/
protected Document buildDocument() {
try {
DocumentBuilder documentBuilder;
synchronized (this.documentBuilderFactoryMonitor) {
if (this.documentBuilderFactory == null) {
this.documentBuilderFactory = createDocumentBuilderFactory();
}
documentBuilder = createDocumentBuilder(this.documentBuilderFactory);
DocumentBuilderFactory builderFactory = this.documentBuilderFactory;
if (builderFactory == null) {
builderFactory = createDocumentBuilderFactory();
this.documentBuilderFactory = builderFactory;
}
return documentBuilder.newDocument();
DocumentBuilder builder = createDocumentBuilder(builderFactory);
return builder.newDocument();
}
catch (ParserConfigurationException ex) {
throw new UnmarshallingFailureException("Could not create document placeholder: " + ex.getMessage(), ex);
Expand Down Expand Up @@ -179,11 +183,11 @@ protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserCon
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
throws ParserConfigurationException {

DocumentBuilder documentBuilder = factory.newDocumentBuilder();
DocumentBuilder builder = factory.newDocumentBuilder();
if (!isProcessExternalEntities()) {
documentBuilder.setEntityResolver(NO_OP_ENTITY_RESOLVER);
builder.setEntityResolver(NO_OP_ENTITY_RESOLVER);
}
return documentBuilder;
return builder;
}

/**
Expand All @@ -193,11 +197,17 @@ protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
* @throws ParserConfigurationException if thrown by JAXP methods
*/
protected XMLReader createXmlReader() throws SAXException, ParserConfigurationException {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
SAXParser saxParser = saxParserFactory.newSAXParser();
SAXParserFactory parserFactory = this.saxParserFactory;
if (parserFactory == null) {
parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
parserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
parserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.saxParserFactory = parserFactory;
}
SAXParser saxParser = parserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) {
xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -61,6 +61,7 @@
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.0
* @see MarshallingHttpMessageConverter
*/
Expand All @@ -70,13 +71,17 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa

private boolean processExternalEntities = false;

@Nullable
private volatile SAXParserFactory sourceParserFactory;


/**
* Indicate whether DTD parsing should be supported.
* <p>Default is {@code false} meaning that DTD is disabled.
*/
public void setSupportDtd(boolean supportDtd) {
this.supportDtd = supportDtd;
this.sourceParserFactory = null;
}

/**
Expand All @@ -97,6 +102,7 @@ public void setProcessExternalEntities(boolean processExternalEntities) {
if (processExternalEntities) {
this.supportDtd = true;
}
this.sourceParserFactory = null;
}

/**
Expand Down Expand Up @@ -156,11 +162,16 @@ protected Source processSource(Source source) {
if (source instanceof StreamSource streamSource) {
InputSource inputSource = new InputSource(streamSource.getInputStream());
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
String featureName = "http://xml.org/sax/features/external-general-entities";
saxParserFactory.setFeature(featureName, isProcessExternalEntities());
SAXParserFactory saxParserFactory = this.sourceParserFactory;
if (saxParserFactory == null) {
saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
saxParserFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd());
saxParserFactory.setFeature(
"http://xml.org/sax/features/external-general-entities", isProcessExternalEntities());
this.sourceParserFactory = saxParserFactory;
}
SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
if (!isProcessExternalEntities()) {
Expand Down
Loading

0 comments on commit a4c2f29

Please sign in to comment.