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