Repository: camel Updated Branches: refs/heads/master 7d781cce7 -> 070ed43e1
CAMEL-9534: XmlConverter: use a pool of SAXParser / XMLReader instances Creating and configuring a new SAXParserFactory and SAXParser / XMLReader for every SAXSource is very expensive. A SAXParser / XMLReader instance can be used to parse many XML documents. Add an XMLReaderPool helper to manage existing XMLReader instances (i.e. hand out existing instances from the pool, reset properties / features and return the instance to the pool after use). Use pooled XMLReaders in XmlConverter.toSAXSource* (except if a specific SAXParserFactory is configured on the Exchange). Signed-off-by: Karsten Blees <karsten.bl...@dcon.de> Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/3c8560f0 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/3c8560f0 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/3c8560f0 Branch: refs/heads/master Commit: 3c8560f003e0a4534cef428664fd4811621aa03c Parents: 7d90d5d Author: Karsten Blees <bl...@dcon.de> Authored: Wed Jan 27 19:10:22 2016 +0100 Committer: Daniel Kulp <dk...@apache.org> Committed: Mon Feb 1 16:06:24 2016 -0500 ---------------------------------------------------------------------- .../camel/converter/jaxp/XMLReaderPool.java | 234 +++++++++++++++++++ .../camel/converter/jaxp/XmlConverter.java | 50 ++-- 2 files changed, 265 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/3c8560f0/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLReaderPool.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLReaderPool.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLReaderPool.java new file mode 100644 index 0000000..ec50920 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLReaderPool.java @@ -0,0 +1,234 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.converter.jaxp; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; + +/** + * Manages a pool of XMLReader (and associated SAXParser) instances for reuse. + */ +public class XMLReaderPool { + private final Queue<WeakReference<XMLReader>> pool = new ConcurrentLinkedQueue<WeakReference<XMLReader>>(); + private final SAXParserFactory saxParserFactory; + + /** + * Creates a new instance. + * + * @param saxParserFactory + * the SAXParserFactory used to create new SAXParser instances + */ + public XMLReaderPool(SAXParserFactory saxParserFactory) { + this.saxParserFactory = saxParserFactory; + } + + /** + * Returns an XMLReader that can be used exactly once. Calling one of the + * {@code parse} methods returns the reader to the pool. This is useful + * for e.g. SAXSource which bundles an XMLReader with an InputSource that + * can also be consumed just once. + * + * @return the XMLReader + * @throws SAXException + * see {@link SAXParserFactory#newSAXParser()} + * @throws ParserConfigurationException + * see {@link SAXParserFactory#newSAXParser()} + */ + public XMLReader createXMLReader() throws SAXException, ParserConfigurationException { + XMLReader xmlReader = null; + WeakReference<XMLReader> ref; + while ((ref = pool.poll()) != null) { + if ((xmlReader = ref.get()) != null) + break; + } + + if (xmlReader == null) { + xmlReader = saxParserFactory.newSAXParser().getXMLReader(); + } + + return new OneTimeXMLReader(xmlReader); + } + + /** + * Wraps another XMLReader for single use only. + */ + private class OneTimeXMLReader implements XMLReader { + private XMLReader xmlReader; + private final Map<String, Boolean> initFeatures = new HashMap<String, Boolean>(); + private final Map<String, Object> initProperties = new HashMap<String, Object>(); + private final ContentHandler initContentHandler; + private final DTDHandler initDtdHandler; + private final EntityResolver initEntityResolver; + private final ErrorHandler initErrorHandler; + + private OneTimeXMLReader(XMLReader xmlReader) { + this.xmlReader = xmlReader; + this.initContentHandler = xmlReader.getContentHandler(); + this.initDtdHandler = xmlReader.getDTDHandler(); + this.initEntityResolver = xmlReader.getEntityResolver(); + this.initErrorHandler = xmlReader.getErrorHandler(); + } + + private void release() { + // reset XMLReader to its initial state + for (Map.Entry<String, Boolean> feature : initFeatures.entrySet()) { + try { + xmlReader.setFeature(feature.getKey(), feature.getValue().booleanValue()); + } catch (Exception e) { + // ignore + } + } + for (Map.Entry<String, Object> property : initProperties.entrySet()) { + try { + xmlReader.setProperty(property.getKey(), property.getValue()); + } catch (Exception e) { + // ignore + } + } + xmlReader.setContentHandler(initContentHandler); + xmlReader.setDTDHandler(initDtdHandler); + xmlReader.setEntityResolver(initEntityResolver); + xmlReader.setErrorHandler(initErrorHandler); + + // return the wrapped instance to the pool + pool.offer(new WeakReference<XMLReader>(xmlReader)); + xmlReader = null; + } + + private void checkValid() { + if (xmlReader == null) + throw new IllegalStateException("OneTimeXMLReader.parse() can only be used once!"); + } + + @Override + public boolean getFeature(String name) + throws SAXNotRecognizedException, SAXNotSupportedException { + checkValid(); + return xmlReader.getFeature(name); + } + + @Override + public void setFeature(String name, boolean value) + throws SAXNotRecognizedException, SAXNotSupportedException { + checkValid(); + if (!initFeatures.containsKey(name)) + initFeatures.put(name, Boolean.valueOf(xmlReader.getFeature(name))); + xmlReader.setFeature(name, value); + } + + @Override + public Object getProperty(String name) + throws SAXNotRecognizedException, SAXNotSupportedException { + checkValid(); + return xmlReader.getProperty(name); + } + + @Override + public void setProperty(String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException { + checkValid(); + if (!initProperties.containsKey(name)) + initProperties.put(name, xmlReader.getProperty(name)); + xmlReader.setProperty(name, value); + } + + @Override + public ContentHandler getContentHandler() { + checkValid(); + return xmlReader.getContentHandler(); + } + + @Override + public void setContentHandler(ContentHandler handler) { + checkValid(); + xmlReader.setContentHandler(handler); + } + + @Override + public DTDHandler getDTDHandler() { + checkValid(); + return xmlReader.getDTDHandler(); + } + + @Override + public void setDTDHandler(DTDHandler handler) { + checkValid(); + xmlReader.setDTDHandler(handler); + } + + @Override + public EntityResolver getEntityResolver() { + checkValid(); + return xmlReader.getEntityResolver(); + } + + @Override + public void setEntityResolver(EntityResolver resolver) { + checkValid(); + xmlReader.setEntityResolver(resolver); + } + + @Override + public ErrorHandler getErrorHandler() { + checkValid(); + return xmlReader.getErrorHandler(); + } + + @Override + public void setErrorHandler(ErrorHandler handler) { + checkValid(); + xmlReader.setErrorHandler(handler); + } + + @Override + public void parse(InputSource input) throws IOException, SAXException { + checkValid(); + try { + xmlReader.parse(input); + } finally { + release(); + } + } + + @Override + public void parse(String systemId) throws IOException, SAXException { + checkValid(); + try { + xmlReader.parse(systemId); + } finally { + release(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/3c8560f0/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java index d29f643..649d327 100644 --- a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java @@ -36,7 +36,6 @@ import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; @@ -91,6 +90,7 @@ public class XmlConverter { private volatile DocumentBuilderFactory documentBuilderFactory; private volatile TransformerFactory transformerFactory; + private volatile XMLReaderPool xmlReaderPool; public XmlConverter() { } @@ -581,31 +581,25 @@ public class XmlConverter { } inputSource.setSystemId(source.getSystemId()); inputSource.setPublicId(source.getPublicId()); + XMLReader xmlReader = null; - SAXParserFactory sfactory = null; - //Need to setup XMLReader security feature by default try { // use the SAXPaserFactory which is set from exchange if (exchange != null) { - sfactory = exchange.getProperty(Exchange.SAXPARSER_FACTORY, SAXParserFactory.class); - } - if (sfactory == null) { - sfactory = SAXParserFactory.newInstance(); - try { - sfactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); - } catch (Exception e) { - LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}.", new Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e}); + SAXParserFactory sfactory = exchange.getProperty(Exchange.SAXPARSER_FACTORY, SAXParserFactory.class); + if (sfactory != null) { + if (!sfactory.isNamespaceAware()) { + sfactory.setNamespaceAware(true); + } + xmlReader = sfactory.newSAXParser().getXMLReader(); } - try { - sfactory.setFeature("http://xml.org/sax/features/external-general-entities", false); - } catch (SAXException e) { - LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}." - , new Object[]{"http://xml.org/sax/features/external-general-entities", false, e}); + } + if (xmlReader == null) { + if (xmlReaderPool == null) { + xmlReaderPool = new XMLReaderPool(createSAXParserFactory()); } + xmlReader = xmlReaderPool.createXMLReader(); } - sfactory.setNamespaceAware(true); - SAXParser parser = sfactory.newSAXParser(); - xmlReader = parser.getXMLReader(); } catch (Exception ex) { LOG.warn("Cannot create the SAXParser XMLReader, due to {}", ex); } @@ -1208,4 +1202,22 @@ public class XmlConverter { } } } + + public SAXParserFactory createSAXParserFactory() { + SAXParserFactory sfactory = SAXParserFactory.newInstance(); + // Need to setup XMLReader security feature by default + try { + sfactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (Exception e) { + LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}.", new Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e}); + } + try { + sfactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + } catch (Exception e) { + LOG.warn("SAXParser doesn't support the feature {} with value {}, due to {}." + , new Object[]{"http://xml.org/sax/features/external-general-entities", false, e}); + } + sfactory.setNamespaceAware(true); + return sfactory; + } }