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 final 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 private boolean readerInvalid; 096 097 private OneTimeXMLReader(XMLReader xmlReader) { 098 this.xmlReader = xmlReader; 099 this.initContentHandler = xmlReader.getContentHandler(); 100 this.initDtdHandler = xmlReader.getDTDHandler(); 101 this.initEntityResolver = xmlReader.getEntityResolver(); 102 this.initErrorHandler = xmlReader.getErrorHandler(); 103 } 104 105 private void release() { 106 try { 107 // reset XMLReader to its initial state 108 for (Map.Entry<String, Boolean> feature : initFeatures.entrySet()) { 109 try { 110 xmlReader.setFeature(feature.getKey(), feature.getValue().booleanValue()); 111 } catch (Exception e) { 112 // ignore 113 } 114 } 115 for (Map.Entry<String, Object> property : initProperties.entrySet()) { 116 try { 117 xmlReader.setProperty(property.getKey(), property.getValue()); 118 } catch (Exception e) { 119 // ignore 120 } 121 } 122 xmlReader.setContentHandler(initContentHandler); 123 xmlReader.setDTDHandler(initDtdHandler); 124 xmlReader.setEntityResolver(initEntityResolver); 125 xmlReader.setErrorHandler(initErrorHandler); 126 127 // return the wrapped instance to the pool 128 pool.offer(new WeakReference<XMLReader>(xmlReader)); 129 } finally { 130 readerInvalid = true; 131 } 132 } 133 134 @Override 135 public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 136 return xmlReader.getFeature(name); 137 } 138 139 @Override 140 public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { 141 if (!readerInvalid) { 142 if (!initFeatures.containsKey(name)) { 143 initFeatures.put(name, Boolean.valueOf(xmlReader.getFeature(name))); 144 } 145 xmlReader.setFeature(name, value); 146 } 147 } 148 149 @Override 150 public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { 151 return xmlReader.getProperty(name); 152 } 153 154 @Override 155 public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { 156 if (!readerInvalid) { 157 if (!initProperties.containsKey(name)) { 158 initProperties.put(name, xmlReader.getProperty(name)); 159 } 160 xmlReader.setProperty(name, value); 161 } 162 } 163 164 @Override 165 public ContentHandler getContentHandler() { 166 return xmlReader.getContentHandler(); 167 } 168 169 @Override 170 public void setContentHandler(ContentHandler handler) { 171 if (!readerInvalid) { 172 xmlReader.setContentHandler(handler); 173 } 174 } 175 176 @Override 177 public DTDHandler getDTDHandler() { 178 return xmlReader.getDTDHandler(); 179 } 180 181 @Override 182 public void setDTDHandler(DTDHandler handler) { 183 if (!readerInvalid) { 184 xmlReader.setDTDHandler(handler); 185 } 186 } 187 188 @Override 189 public EntityResolver getEntityResolver() { 190 return xmlReader.getEntityResolver(); 191 } 192 193 @Override 194 public void setEntityResolver(EntityResolver resolver) { 195 if (!readerInvalid) { 196 xmlReader.setEntityResolver(resolver); 197 } 198 } 199 200 @Override 201 public ErrorHandler getErrorHandler() { 202 return xmlReader.getErrorHandler(); 203 } 204 205 @Override 206 public void setErrorHandler(ErrorHandler handler) { 207 if (!readerInvalid) { 208 xmlReader.setErrorHandler(handler); 209 } 210 } 211 212 @Override 213 public synchronized void parse(InputSource input) throws IOException, SAXException { 214 checkValid(); 215 try { 216 xmlReader.parse(input); 217 } finally { 218 release(); 219 } 220 } 221 222 @Override 223 public synchronized void parse(String systemId) throws IOException, SAXException { 224 checkValid(); 225 try { 226 xmlReader.parse(systemId); 227 } finally { 228 release(); 229 } 230 } 231 232 private void checkValid() { 233 if (readerInvalid) { 234 throw new IllegalStateException("OneTimeXMLReader can only be used once!"); 235 } 236 } 237 } 238}