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.*;
020import java.security.AccessController;
021import java.security.PrivilegedAction;
022import java.util.concurrent.BlockingQueue;
023import java.util.concurrent.LinkedBlockingQueue;
024
025import javax.xml.stream.XMLEventReader;
026import javax.xml.stream.XMLEventWriter;
027import javax.xml.stream.XMLInputFactory;
028import javax.xml.stream.XMLOutputFactory;
029import javax.xml.stream.XMLResolver;
030import javax.xml.stream.XMLStreamException;
031import javax.xml.stream.XMLStreamReader;
032import javax.xml.stream.XMLStreamWriter;
033import javax.xml.transform.Result;
034import javax.xml.transform.Source;
035
036import org.apache.camel.Converter;
037import org.apache.camel.Exchange;
038import org.apache.camel.util.IOHelper;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * A converter of StAX objects
044 *
045 * @version 
046 */
047@Converter
048public class StaxConverter {
049    private static final Logger LOG = LoggerFactory.getLogger(StaxConverter.class);
050
051    private static final BlockingQueue<XMLInputFactory> INPUT_FACTORY_POOL;
052    private static final BlockingQueue<XMLOutputFactory> OUTPUT_FACTORY_POOL;
053    static {
054        int i = 20;
055        try {
056            String s = AccessController.doPrivileged(new PrivilegedAction<String>() {
057                @Override
058                public String run() {
059                    return System.getProperty("org.apache.cxf.staxutils.pool-size", "-1");
060                }
061            });
062            i = Integer.parseInt(s);
063        } catch (Throwable t) {
064            //ignore 
065            i = 20;
066        }
067        try {
068            // if we have more cores than 20, then use that
069            int cores = Runtime.getRuntime().availableProcessors();
070            if (cores > i) {
071                i = cores;
072            }
073        } catch (Throwable t) {
074            // ignore
075            i = 20;
076        }
077
078        if (i <= 0) {
079            i = 20;
080        }
081
082        LOG.debug("StaxConverter pool size: {}", i);
083
084        INPUT_FACTORY_POOL = new LinkedBlockingQueue<>(i);
085        OUTPUT_FACTORY_POOL = new LinkedBlockingQueue<>(i);
086    }
087    
088    private XMLInputFactory inputFactory;
089    private XMLOutputFactory outputFactory;
090
091    @Converter
092    public XMLEventWriter createXMLEventWriter(OutputStream out, Exchange exchange) throws XMLStreamException {
093        XMLOutputFactory factory = getOutputFactory();
094        try {
095            return factory.createXMLEventWriter(IOHelper.buffered(out), IOHelper.getCharsetName(exchange));
096        } finally {
097            returnXMLOutputFactory(factory);
098        }
099    }
100    
101    @Converter
102    public XMLEventWriter createXMLEventWriter(Writer writer) throws XMLStreamException {
103        XMLOutputFactory factory = getOutputFactory();
104        try {
105            return factory.createXMLEventWriter(IOHelper.buffered(writer));
106        } finally {
107            returnXMLOutputFactory(factory);
108        }
109    }
110
111    @Converter
112    public XMLEventWriter createXMLEventWriter(Result result) throws XMLStreamException {
113        XMLOutputFactory factory = getOutputFactory();
114        try {
115            return factory.createXMLEventWriter(result);
116        } finally {
117            returnXMLOutputFactory(factory);
118        }
119    }
120    
121    @Converter
122    public XMLStreamWriter createXMLStreamWriter(OutputStream outputStream, Exchange exchange) throws XMLStreamException {
123        XMLOutputFactory factory = getOutputFactory();
124        try {
125            return factory.createXMLStreamWriter(IOHelper.buffered(outputStream), IOHelper.getCharsetName(exchange));
126        } finally {
127            returnXMLOutputFactory(factory);
128        }
129    }
130
131    @Converter
132    public XMLStreamWriter createXMLStreamWriter(Writer writer) throws XMLStreamException {
133        XMLOutputFactory factory = getOutputFactory();
134        try {
135            return factory.createXMLStreamWriter(IOHelper.buffered(writer));
136        } finally {
137            returnXMLOutputFactory(factory);
138        }
139    }
140
141    @Converter
142    public XMLStreamWriter createXMLStreamWriter(Result result) throws XMLStreamException {
143        XMLOutputFactory factory = getOutputFactory();
144        try {
145            return factory.createXMLStreamWriter(result);
146        } finally {
147            returnXMLOutputFactory(factory);
148        }
149    }
150    
151    /**
152     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
153     */
154    @Deprecated
155    public XMLStreamReader createXMLStreamReader(InputStream in) throws XMLStreamException {
156        XMLInputFactory factory = getInputFactory();
157        try {
158            return factory.createXMLStreamReader(IOHelper.buffered(in));
159        } finally {
160            returnXMLInputFactory(factory);
161        }
162    }
163    
164    @Converter
165    public XMLStreamReader createXMLStreamReader(InputStream in, Exchange exchange) throws XMLStreamException {
166        XMLInputFactory factory = getInputFactory();
167        try {
168            String charsetName = IOHelper.getCharsetName(exchange, false);
169            if (charsetName == null) {
170                return factory.createXMLStreamReader(IOHelper.buffered(in));
171            } else {
172                return factory.createXMLStreamReader(IOHelper.buffered(in), charsetName);
173            }
174        } finally {
175            returnXMLInputFactory(factory);
176        }
177    }
178
179    @Converter
180    public XMLStreamReader createXMLStreamReader(File file, Exchange exchange) throws XMLStreamException, FileNotFoundException {
181        XMLInputFactory factory = getInputFactory();
182        try {
183            return factory.createXMLStreamReader(IOHelper.buffered(new FileInputStream(file)), IOHelper.getCharsetName(exchange));
184        } finally {
185            returnXMLInputFactory(factory);
186        }
187    }
188
189    @Converter
190    public XMLStreamReader createXMLStreamReader(Reader reader) throws XMLStreamException {
191        XMLInputFactory factory = getInputFactory();
192        try {
193            return factory.createXMLStreamReader(IOHelper.buffered(reader));
194        } finally {
195            returnXMLInputFactory(factory);
196        }
197    }
198
199    @Converter
200    public XMLStreamReader createXMLStreamReader(Source in) throws XMLStreamException {
201        XMLInputFactory factory = getInputFactory();
202        try {
203            return factory.createXMLStreamReader(in);
204        } finally {
205            returnXMLInputFactory(factory);
206        }
207    }
208
209    @Converter
210    public XMLStreamReader createXMLStreamReader(String string) throws XMLStreamException {
211        XMLInputFactory factory = getInputFactory();
212        try {
213            return factory.createXMLStreamReader(new StringReader(string));
214        } finally {
215            returnXMLInputFactory(factory);
216        }
217    }
218    
219    /**
220     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
221     */
222    @Deprecated
223    public XMLEventReader createXMLEventReader(InputStream in) throws XMLStreamException {
224        XMLInputFactory factory = getInputFactory();
225        try {
226            return factory.createXMLEventReader(IOHelper.buffered(in));
227        } finally {
228            returnXMLInputFactory(factory);
229        }
230    }
231
232    @Converter
233    public XMLEventReader createXMLEventReader(InputStream in, Exchange exchange) throws XMLStreamException {
234        XMLInputFactory factory = getInputFactory();
235        try {
236            String charsetName = IOHelper.getCharsetName(exchange, false);
237            if (charsetName == null) {
238                return factory.createXMLEventReader(IOHelper.buffered(in));
239            } else {
240                return factory.createXMLEventReader(IOHelper.buffered(in), charsetName);
241            }
242        } finally {
243            returnXMLInputFactory(factory);
244        }
245    }
246
247    @Converter
248    public XMLEventReader createXMLEventReader(File file, Exchange exchange) throws XMLStreamException, FileNotFoundException {
249        XMLInputFactory factory = getInputFactory();
250        try {
251            return factory.createXMLEventReader(IOHelper.buffered(new FileInputStream(file)), IOHelper.getCharsetName(exchange));
252        } finally {
253            returnXMLInputFactory(factory);
254        }
255    }
256
257    @Converter
258    public XMLEventReader createXMLEventReader(Reader reader) throws XMLStreamException {
259        XMLInputFactory factory = getInputFactory();
260        try {
261            return factory.createXMLEventReader(IOHelper.buffered(reader));
262        } finally {
263            returnXMLInputFactory(factory);
264        }
265    }
266
267    @Converter
268    public XMLEventReader createXMLEventReader(XMLStreamReader reader) throws XMLStreamException {
269        XMLInputFactory factory = getInputFactory();
270        try {
271            return factory.createXMLEventReader(reader);
272        } finally {
273            returnXMLInputFactory(factory);
274        }
275    }
276
277    @Converter
278    public XMLEventReader createXMLEventReader(Source in) throws XMLStreamException {
279        XMLInputFactory factory = getInputFactory();
280        try {
281            return factory.createXMLEventReader(in);
282        } finally {
283            returnXMLInputFactory(factory);
284        }
285    }
286
287    @Converter
288    public InputStream createInputStream(XMLStreamReader reader, Exchange exchange) {
289        XMLOutputFactory factory = getOutputFactory();
290        try {
291            String charsetName = IOHelper.getCharsetName(exchange, false);
292            return new XMLStreamReaderInputStream(reader, charsetName, factory);
293        } finally {
294            returnXMLOutputFactory(factory);
295        }
296    }
297
298    @Converter
299    public Reader createReader(XMLStreamReader reader, Exchange exchange) {
300        XMLOutputFactory factory = getOutputFactory();
301        try {
302            return new XMLStreamReaderReader(reader, factory);
303        } finally {
304            returnXMLOutputFactory(factory);
305        }
306    }
307
308    private static boolean isWoodstox(Object factory) {
309        return factory.getClass().getPackage().getName().startsWith("com.ctc.wstx");
310    }
311
312    private XMLInputFactory getXMLInputFactory() {
313        XMLInputFactory f = INPUT_FACTORY_POOL.poll();
314        if (f == null) {
315            f = createXMLInputFactory(true);
316        }
317        return f;
318    }
319    
320    private void returnXMLInputFactory(XMLInputFactory factory) {
321        if (factory != inputFactory) {
322            INPUT_FACTORY_POOL.offer(factory);
323        }
324    }
325    
326    private XMLOutputFactory getXMLOutputFactory() {
327        XMLOutputFactory f = OUTPUT_FACTORY_POOL.poll();
328        if (f == null) {
329            f = XMLOutputFactory.newInstance();
330        }
331        return f;
332    }
333    
334    private void returnXMLOutputFactory(XMLOutputFactory factory) {
335        if (factory != outputFactory) {
336            OUTPUT_FACTORY_POOL.offer(factory);
337        }
338    }
339    
340    public static XMLInputFactory createXMLInputFactory(boolean nsAware) {
341        XMLInputFactory factory = XMLInputFactory.newInstance();
342        setProperty(factory, XMLInputFactory.IS_NAMESPACE_AWARE, nsAware);
343        setProperty(factory, XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
344        setProperty(factory, XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
345        setProperty(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
346        factory.setXMLResolver(new XMLResolver() {
347            public Object resolveEntity(String publicID, String systemID,
348                                        String baseURI, String namespace)
349                throws XMLStreamException {
350                throw new XMLStreamException("Reading external entities is disabled");
351            }
352        });
353
354        if (isWoodstox(factory)) {
355            // just log a debug as we are good then
356            LOG.debug("Created Woodstox XMLInputFactory: {}", factory);
357        } else {
358            // log a hint that woodstock may be a better factory to use
359            LOG.info("Created XMLInputFactory: {}. DOMSource/DOMResult may have issues with {}. We suggest using Woodstox.", factory, factory);
360        }
361        return factory;
362    }
363    
364    private static void setProperty(XMLInputFactory f, String p, Object o) {
365        try {
366            f.setProperty(p,  o);
367        } catch (Throwable t) {
368            //ignore
369        }
370    }
371    
372    // Properties
373    //-------------------------------------------------------------------------
374
375    public XMLInputFactory getInputFactory() {
376        if (inputFactory == null) {
377            return getXMLInputFactory();
378        }
379        return inputFactory;
380    }
381
382    public XMLOutputFactory getOutputFactory() {
383        if (outputFactory == null) {
384            return getXMLOutputFactory();
385        }
386        return outputFactory;
387    }
388    
389    public void setInputFactory(XMLInputFactory inputFactory) {
390        this.inputFactory = inputFactory;
391    }
392
393    public void setOutputFactory(XMLOutputFactory outputFactory) {
394        this.outputFactory = outputFactory;
395    }
396
397}