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}