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