001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.converter.jaxp; 018 019import java.io.IOException; 020import java.lang.ref.WeakReference; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Queue; 024import java.util.concurrent.ConcurrentLinkedQueue; 025 026import javax.xml.parsers.ParserConfigurationException; 027import javax.xml.parsers.SAXParserFactory; 028 029import org.xml.sax.ContentHandler; 030import org.xml.sax.DTDHandler; 031import org.xml.sax.EntityResolver; 032import org.xml.sax.ErrorHandler; 033import org.xml.sax.InputSource; 034import org.xml.sax.SAXException; 035import org.xml.sax.SAXNotRecognizedException; 036import org.xml.sax.SAXNotSupportedException; 037import org.xml.sax.XMLReader; 038 039/** 040 * Manages a pool of XMLReader (and associated SAXParser) instances for reuse. 041 */ 042public class XMLReaderPool { 043 private final Queue<WeakReference<XMLReader>> pool = new ConcurrentLinkedQueue<WeakReference<XMLReader>>(); 044 private final SAXParserFactory saxParserFactory; 045 046 /** 047 * Creates a new instance. 048 * 049 * @param saxParserFactory 050 * the SAXParserFactory used to create new SAXParser instances 051 */ 052 public XMLReaderPool(SAXParserFactory saxParserFactory) { 053 this.saxParserFactory = saxParserFactory; 054 } 055 056 /** 057 * Returns an XMLReader that can be used exactly once. Calling one of the 058 * {@code parse} methods returns the reader to the pool. This is useful 059 * for e.g. SAXSource which bundles an XMLReader with an InputSource that 060 * can also be consumed just once. 061 * 062 * @return the XMLReader 063 * @throws SAXException 064 * see {@link SAXParserFactory#newSAXParser()} 065 * @throws ParserConfigurationException 066 * see {@link SAXParserFactory#newSAXParser()} 067 */ 068 public XMLReader createXMLReader() throws SAXException, ParserConfigurationException { 069 XMLReader xmlReader = null; 070 WeakReference<XMLReader> ref; 071 while ((ref = pool.poll()) != null) { 072 if ((xmlReader = ref.get()) != null) { 073 break; 074 } 075 } 076 077 if (xmlReader == null) { 078 xmlReader = saxParserFactory.newSAXParser().getXMLReader(); 079 } 080 081 return new OneTimeXMLReader(xmlReader); 082 } 083 084 /** 085 * Wraps another XMLReader for single use only. 086 */ 087 private final class OneTimeXMLReader implements XMLReader { 088 private XMLReader xmlReader; 089 private final Map<String, Boolean> initFeatures = new HashMap<String, Boolean>(); 090 private final Map<String, Object> initProperties = new HashMap<String, Object>(); 091 private final ContentHandler initContentHandler; 092 private final DTDHandler initDtdHandler; 093 private final EntityResolver initEntityResolver; 094 private final ErrorHandler initErrorHandler; 095 096 private OneTimeXMLReader(XMLReader xmlReader) { 097 this.xmlReader = xmlReader; 098 this.initContentHandler = xmlReader.getContentHandler(); 099 this.initDtdHandler = xmlReader.getDTDHandler(); 100 this.initEntityResolver = xmlReader.getEntityResolver(); 101 this.initErrorHandler = xmlReader.getErrorHandler(); 102 } 103 104 private void release() { 105 // reset XMLReader to its initial state 106 for (Map.Entry<String, Boolean> feature : initFeatures.entrySet()) { 107 try { 108 xmlReader.setFeature(feature.getKey(), feature.getValue().booleanValue()); 109 } catch (Exception e) { 110 // ignore 111 } 112 } 113 for (Map.Entry<String, Object> property : initProperties.entrySet()) { 114 try { 115 xmlReader.setProperty(property.getKey(), property.getValue()); 116 } catch (Exception e) { 117 // ignore 118 } 119 } 120 xmlReader.setContentHandler(initContentHandler); 121 xmlReader.setDTDHandler(initDtdHandler); 122 xmlReader.setEntityResolver(initEntityResolver); 123 xmlReader.setErrorHandler(initErrorHandler); 124 125 // return the wrapped instance to the pool 126 pool.offer(new WeakReference<XMLReader>(xmlReader)); 127 xmlReader = null; 128 } 129 130 private void checkValid() { 131 if (xmlReader == null) { 132 throw new IllegalStateException("OneTimeXMLReader.parse() can only be used once!"); 133 } 134 } 135 136 @Override 137 public boolean getFeature(String name) 138 throws SAXNotRecognizedException, SAXNotSupportedException { 139 checkValid(); 140 return xmlReader.getFeature(name); 141 } 142 143 @Override 144 public void setFeature(String name, boolean value) 145 throws SAXNotRecognizedException, SAXNotSupportedException { 146 checkValid(); 147 if (!initFeatures.containsKey(name)) { 148 initFeatures.put(name, Boolean.valueOf(xmlReader.getFeature(name))); 149 } 150 xmlReader.setFeature(name, value); 151 } 152 153 @Override 154 public Object getProperty(String name) 155 throws SAXNotRecognizedException, SAXNotSupportedException { 156 checkValid(); 157 return xmlReader.getProperty(name); 158 } 159 160 @Override 161 public void setProperty(String name, Object value) 162 throws SAXNotRecognizedException, SAXNotSupportedException { 163 checkValid(); 164 if (!initProperties.containsKey(name)) { 165 initProperties.put(name, xmlReader.getProperty(name)); 166 } 167 xmlReader.setProperty(name, value); 168 } 169 170 @Override 171 public ContentHandler getContentHandler() { 172 checkValid(); 173 return xmlReader.getContentHandler(); 174 } 175 176 @Override 177 public void setContentHandler(ContentHandler handler) { 178 checkValid(); 179 xmlReader.setContentHandler(handler); 180 } 181 182 @Override 183 public DTDHandler getDTDHandler() { 184 checkValid(); 185 return xmlReader.getDTDHandler(); 186 } 187 188 @Override 189 public void setDTDHandler(DTDHandler handler) { 190 checkValid(); 191 xmlReader.setDTDHandler(handler); 192 } 193 194 @Override 195 public EntityResolver getEntityResolver() { 196 checkValid(); 197 return xmlReader.getEntityResolver(); 198 } 199 200 @Override 201 public void setEntityResolver(EntityResolver resolver) { 202 checkValid(); 203 xmlReader.setEntityResolver(resolver); 204 } 205 206 @Override 207 public ErrorHandler getErrorHandler() { 208 checkValid(); 209 return xmlReader.getErrorHandler(); 210 } 211 212 @Override 213 public void setErrorHandler(ErrorHandler handler) { 214 checkValid(); 215 xmlReader.setErrorHandler(handler); 216 } 217 218 @Override 219 public void parse(InputSource input) throws IOException, SAXException { 220 checkValid(); 221 try { 222 xmlReader.parse(input); 223 } finally { 224 release(); 225 } 226 } 227 228 @Override 229 public void parse(String systemId) throws IOException, SAXException { 230 checkValid(); 231 try { 232 xmlReader.parse(systemId); 233 } finally { 234 release(); 235 } 236 } 237 } 238}