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}