From ea14a83785ace0a53bb4581b2ba684d9b13ccd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JB=20Onofr=C3=A9?= Date: Sun, 5 Apr 2026 17:32:47 +0200 Subject: [PATCH] Remove static ThreadLocal caching in XmlUtils to prevent ClassLoader leaks The static ThreadLocal fields holding DocumentBuilderFactory, TransformerFactory and SAXParserFactory pin the ClassLoader of the XML provider bundle on long-lived Karaf worker threads. When that bundle is updated or refreshed, the old ClassLoader can never be GC'd, leading to zombie bundles and Metaspace growth. Create fresh factory instances per call instead of caching them. The public API is unchanged. Fixes #2278 --- .../java/org/apache/karaf/util/XmlUtils.java | 98 ++++++++----------- 1 file changed, 39 insertions(+), 59 deletions(-) diff --git a/util/src/main/java/org/apache/karaf/util/XmlUtils.java b/util/src/main/java/org/apache/karaf/util/XmlUtils.java index 915dad54580..4c6acc006f9 100644 --- a/util/src/main/java/org/apache/karaf/util/XmlUtils.java +++ b/util/src/main/java/org/apache/karaf/util/XmlUtils.java @@ -46,10 +46,6 @@ public class XmlUtils { private static final Logger LOGGER = LoggerFactory.getLogger(XmlUtils.class); - private static final ThreadLocal DOCUMENT_BUILDER_FACTORY = new ThreadLocal<>(); - private static final ThreadLocal TRANSFORMER_FACTORY = new ThreadLocal<>(); - private static final ThreadLocal SAX_PARSER_FACTORY = new ThreadLocal<>(); - public static Document parse(String uri) throws TransformerException, IOException, SAXException, ParserConfigurationException { DocumentBuilder db = documentBuilder(); try { @@ -106,74 +102,58 @@ public static void transform(Source xsltSource, Source xmlSource, Result outputT } public static XMLReader xmlReader() throws ParserConfigurationException, SAXException { - SAXParserFactory spf = SAX_PARSER_FACTORY.get(); - if (spf == null) { - spf = SAXParserFactory.newInstance(); - spf.setNamespaceAware(true); - spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); - spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - spf.setFeature("http://xml.org/sax/features/external-general-entities", false); - spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - spf.setXIncludeAware(false); - SAX_PARSER_FACTORY.set(spf); - } + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(true); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + spf.setXIncludeAware(false); return spf.newSAXParser().getXMLReader(); } public static DocumentBuilder documentBuilder() throws ParserConfigurationException { - DocumentBuilderFactory dbf = DOCUMENT_BUILDER_FACTORY.get(); - if (dbf == null) { - dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DOCUMENT_BUILDER_FACTORY.set(dbf); - } + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); return dbf.newDocumentBuilder(); } public static Transformer transformer() throws TransformerConfigurationException { - TransformerFactory tf = TRANSFORMER_FACTORY.get(); - if (tf == null) { - tf = TransformerFactory.newInstance(); - tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); - try { - tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } catch (IllegalArgumentException e) { - LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_DTD); - } - try { - tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); - } catch (IllegalArgumentException e) { - LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_STYLESHEET); - } - TRANSFORMER_FACTORY.set(tf); + TransformerFactory tf = TransformerFactory.newInstance(); + tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + try { + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (IllegalArgumentException e) { + LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_DTD); + } + try { + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + } catch (IllegalArgumentException e) { + LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_STYLESHEET); } return tf.newTransformer(); } private static Transformer transformer(Source xsltSource) throws TransformerConfigurationException { - TransformerFactory tf = TRANSFORMER_FACTORY.get(); - if (tf == null) { - tf = TransformerFactory.newInstance(); - tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); - try { - tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } catch (IllegalArgumentException e) { - LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_DTD); - } - try { - tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); - } catch (IllegalArgumentException e) { - LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_STYLESHEET); - } - TRANSFORMER_FACTORY.set(tf); + TransformerFactory tf = TransformerFactory.newInstance(); + tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + try { + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (IllegalArgumentException e) { + LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_DTD); + } + try { + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + } catch (IllegalArgumentException e) { + LOGGER.warn("XSL transformer implementation doesn't support {} feature", XMLConstants.ACCESS_EXTERNAL_STYLESHEET); } return tf.newTransformer(xsltSource); }