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;
018
019import java.io.BufferedReader;
020import java.io.BufferedWriter;
021import java.io.ByteArrayInputStream;
022import java.io.ByteArrayOutputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.InputStreamReader;
030import java.io.ObjectInput;
031import java.io.ObjectInputStream;
032import java.io.ObjectOutput;
033import java.io.ObjectOutputStream;
034import java.io.ObjectStreamClass;
035import java.io.OutputStream;
036import java.io.OutputStreamWriter;
037import java.io.Reader;
038import java.io.StringReader;
039import java.io.UnsupportedEncodingException;
040import java.io.Writer;
041import java.net.URL;
042import java.nio.ByteBuffer;
043import java.nio.CharBuffer;
044import java.nio.charset.Charset;
045import java.nio.charset.UnsupportedCharsetException;
046import java.util.Properties;
047import java.util.function.Supplier;
048
049import org.apache.camel.Converter;
050import org.apache.camel.Exchange;
051import org.apache.camel.util.IOHelper;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055/**
056 * Some core java.io based <a
057 * href="http://camel.apache.org/type-converter.html">Type Converters</a>
058 *
059 * @version 
060 */
061@Converter
062public final class IOConverter {
063
064    static Supplier<Charset> defaultCharset = Charset::defaultCharset;
065
066    private static final Logger LOG = LoggerFactory.getLogger(IOConverter.class);
067
068    /**
069     * Utility classes should not have a public constructor.
070     */
071    private IOConverter() {
072    }
073
074    @Converter
075    public static InputStream toInputStream(URL url) throws IOException {
076        return IOHelper.buffered(url.openStream());
077    }
078
079    @Converter
080    public static InputStream toInputStream(File file) throws IOException {
081        return IOHelper.buffered(new FileInputStream(file));
082    }
083
084    /**
085     * Converts the given {@link File} with the given charset to {@link InputStream} with the JVM default charset
086     *
087     * @param file the file to be converted
088     * @param charset the charset the file is read with
089     * @return the input stream with the JVM default charset
090     */
091    public static InputStream toInputStream(File file, String charset) throws IOException {
092        if (charset != null) {
093            return new EncodingInputStream(file, charset);
094        } else {
095            return toInputStream(file);
096        }
097    }
098
099    /**
100     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
101     */
102    @Deprecated
103    public static BufferedReader toReader(File file) throws IOException {
104        return toReader(file, (String) null);
105    }
106
107    @Converter
108    public static BufferedReader toReader(File file, Exchange exchange) throws IOException {
109        return toReader(file, IOHelper.getCharsetName(exchange));
110    }
111
112    public static BufferedReader toReader(File file, String charset) throws IOException {
113        FileInputStream in = new FileInputStream(file);
114        return IOHelper.buffered(new EncodingFileReader(in, charset));
115    }
116
117    @Converter
118    public static File toFile(String name) {
119        return new File(name);
120    }
121
122    @Converter
123    public static OutputStream toOutputStream(File file) throws FileNotFoundException {
124        return IOHelper.buffered(new FileOutputStream(file));
125    }
126
127    /**
128     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
129     */
130    @Deprecated
131    public static BufferedWriter toWriter(File file) throws IOException {
132        FileOutputStream os = new FileOutputStream(file, false);
133        return toWriter(os, IOHelper.getCharsetName(null, true));
134    }
135    
136    @Converter
137    public static BufferedWriter toWriter(File file, Exchange exchange) throws IOException {
138        FileOutputStream os = new FileOutputStream(file, false);
139        return toWriter(os, IOHelper.getCharsetName(exchange));
140    }
141
142    public static BufferedWriter toWriter(File file, boolean append, String charset) throws IOException {
143        return toWriter(new FileOutputStream(file, append), charset);
144    }
145
146    public static BufferedWriter toWriter(FileOutputStream os, String charset) throws IOException {
147        return IOHelper.buffered(new EncodingFileWriter(os, charset));
148    }
149
150    /**
151     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
152     */
153    @Deprecated
154    public static Reader toReader(InputStream in) throws IOException {
155        return toReader(in, null);
156    }
157
158    @Converter
159    public static Reader toReader(InputStream in, Exchange exchange) throws IOException {
160        return IOHelper.buffered(new InputStreamReader(in, IOHelper.getCharsetName(exchange)));
161    }
162
163    @Converter
164    public static Reader toReader(byte[] data, Exchange exchange) throws IOException {
165        return toReader(new ByteArrayInputStream(data), exchange);
166    }
167
168    /**
169     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
170     */
171    @Deprecated
172    public static Writer toWriter(OutputStream out) throws IOException {
173        return toWriter(out, null);
174    }
175    
176    @Converter
177    public static Writer toWriter(OutputStream out, Exchange exchange) throws IOException {
178        return IOHelper.buffered(new OutputStreamWriter(out, IOHelper.getCharsetName(exchange)));
179    }
180
181    @Converter
182    public static StringReader toReader(String text) {
183        // no buffering required as the complete string input is already passed
184        // over as a whole
185        return new StringReader(text);
186    }
187
188    /**
189     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
190     */
191    @Deprecated
192    public static InputStream toInputStream(String text) throws IOException {
193        return toInputStream(text, null);
194    }
195    
196    @Converter
197    public static InputStream toInputStream(String text, Exchange exchange) throws IOException {
198        return toInputStream(text.getBytes(IOHelper.getCharsetName(exchange)));
199    }
200    
201    @Converter
202    public static InputStream toInputStream(StringBuffer buffer, Exchange exchange) throws IOException {
203        return toInputStream(buffer.toString(), exchange);
204    }
205    
206    @Converter
207    public static InputStream toInputStream(StringBuilder builder, Exchange exchange) throws IOException {
208        return toInputStream(builder.toString(), exchange);
209    }
210    
211    /**
212     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
213     */
214    @Deprecated
215    public static InputStream toInputStream(BufferedReader buffer) throws IOException {
216        return toInputStream(buffer, null);
217    }
218    
219    @Converter
220    public static InputStream toInputStream(BufferedReader buffer, Exchange exchange) throws IOException {
221        return toInputStream(toString(buffer), exchange);
222    }
223
224    /**
225     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
226     */
227    @Deprecated
228    public static String toString(byte[] data) throws IOException {
229        return toString(data, null);
230    }
231    
232    @Converter
233    public static String toString(byte[] data, Exchange exchange) throws IOException {
234        return new String(data, IOHelper.getCharsetName(exchange));
235    }
236
237    /**
238     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
239     */
240    @Deprecated
241    public static String toString(File file) throws IOException {
242        return toString(file, null);
243    }
244    
245    @Converter
246    public static String toString(File file, Exchange exchange) throws IOException {
247        return toString(toReader(file, exchange));
248    }
249
250    @Converter
251    public static byte[] toByteArray(File file) throws IOException {
252        InputStream is = toInputStream(file);
253        try {
254            return toBytes(is);
255        } finally {
256            IOHelper.close(is, "file", LOG);
257        }
258    }
259    
260    /**
261     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
262     */
263    @Deprecated
264    public static byte[] toByteArray(Reader reader) throws IOException {
265        return toByteArray(reader, null);
266    }
267    
268    @Converter
269    public static byte[] toByteArray(Reader reader, Exchange exchange) throws IOException {
270        return toByteArray(IOHelper.buffered(reader), exchange);
271    }
272
273    /**
274     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
275     */
276    @Deprecated
277    public static String toString(URL url) throws IOException {
278        return toString(url, null);
279    }
280
281    @Converter
282    public static String toString(URL url, Exchange exchange) throws IOException {
283        InputStream is = toInputStream(url);
284        try {
285            return toString(is, exchange);
286        } finally {
287            IOHelper.close(is, "url", LOG);
288        }
289    }
290
291    @Converter
292    public static String toString(Reader reader) throws IOException {
293        return toString(IOHelper.buffered(reader));
294    }
295
296    @Converter
297    public static String toString(BufferedReader reader) throws IOException {
298        StringBuilder sb = new StringBuilder(1024);
299        char[] buf = new char[1024];
300        try {
301            int len;
302            // read until we reach then end which is the -1 marker
303            while ((len = reader.read(buf)) != -1) {
304                sb.append(buf, 0, len);
305            }
306        } finally {
307            IOHelper.close(reader, "reader", LOG);
308        }
309
310        return sb.toString();
311    }
312    
313    /**
314     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
315     */
316    @Deprecated
317    public static byte[] toByteArray(BufferedReader reader) throws IOException {
318        return toByteArray(reader, null);
319    }
320    
321    @Converter
322    public static byte[] toByteArray(BufferedReader reader, Exchange exchange) throws IOException {
323        String s = toString(reader);
324        return toByteArray(s, exchange);
325    }
326
327    /**
328     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
329     */
330    @Deprecated
331    public static byte[] toByteArray(String value) throws IOException {
332        return toByteArray(value, null);
333    }
334
335    @Converter
336    public static byte[] toByteArray(String value, Exchange exchange) throws IOException {
337        return value.getBytes(IOHelper.getCharsetName(exchange));
338    }
339
340    /**
341     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
342     */
343    @Deprecated
344    public static String toString(InputStream in) throws IOException {
345        return toString(in, null);
346    }
347
348    @Converter
349    public static String toString(InputStream in, Exchange exchange) throws IOException {
350        return toString(toReader(in, exchange));
351    }
352
353    @Converter
354    public static InputStream toInputStream(byte[] data) {
355        // no buffering required as the complete byte input is already passed
356        // over as a whole
357        return new ByteArrayInputStream(data);
358    }
359
360    @Converter
361    public static ObjectOutput toObjectOutput(OutputStream stream) throws IOException {
362        if (stream instanceof ObjectOutput) {
363            return (ObjectOutput) stream;
364        } else {
365            return new ObjectOutputStream(IOHelper.buffered(stream));
366        }
367    }
368
369    @Converter
370    public static ObjectInput toObjectInput(final InputStream stream, final Exchange exchange) throws IOException {
371        if (stream instanceof ObjectInput) {
372            return (ObjectInput) stream;
373        } else {
374            return new ObjectInputStream(IOHelper.buffered(stream)) {
375                @Override
376                protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
377                    // need to let Camel be able to resolve class using ClassResolver SPI, to let class loading
378                    // work in OSGi and other containers
379                    Class<?>  answer = null;
380                    String name = objectStreamClass.getName();
381                    if (exchange != null) {
382                        LOG.trace("Loading class {} using Camel ClassResolver", name);
383                        answer = exchange.getContext().getClassResolver().resolveClass(name);
384                    }
385                    if (answer == null) {
386                        LOG.trace("Loading class {} using JDK default implementation", name);
387                        answer = super.resolveClass(objectStreamClass);
388                    }
389                    return answer;
390                }
391            };
392        }
393    }
394
395    @Converter
396    public static byte[] toBytes(InputStream stream) throws IOException {
397        ByteArrayOutputStream bos = new ByteArrayOutputStream();
398        IOHelper.copy(IOHelper.buffered(stream), bos);
399
400        // no need to close the ByteArrayOutputStream as it's close()
401        // implementation is noop
402        return bos.toByteArray();
403    }
404
405    @Converter
406    public static byte[] toByteArray(ByteArrayOutputStream os) {
407        return os.toByteArray();
408    }
409
410    /**
411     * @deprecated will be removed in Camel 3.0. Use the method which has 2 parameters.
412     */
413    @Deprecated
414    public static String toString(ByteArrayOutputStream os) throws IOException {
415        return toString(os, null);
416    }
417
418    @Converter
419    public static String toString(ByteArrayOutputStream os, Exchange exchange) throws IOException {
420        return os.toString(IOHelper.getCharsetName(exchange));
421    }
422
423    @Converter
424    public static InputStream toInputStream(ByteArrayOutputStream os) {
425        // no buffering required as the complete byte array input is already
426        // passed over as a whole
427        return new ByteArrayInputStream(os.toByteArray());
428    }
429
430    @Converter
431    public static Properties toProperties(File file) throws IOException {
432        return toProperties(new FileInputStream(file));
433    }
434
435    @Converter
436    public static Properties toProperties(InputStream is) throws IOException {
437        Properties prop = new Properties();
438        try {
439            prop.load(is);
440        } finally {
441            IOHelper.close(is);
442        }
443        return prop;
444    }
445
446    @Converter
447    public static Properties toProperties(Reader reader) throws IOException {
448        Properties prop = new Properties();
449        try {
450            prop.load(reader);
451        } finally {
452            IOHelper.close(reader);
453        }
454        return prop;
455    }
456
457    /**
458     * Gets the charset name if set as header or property {@link Exchange#CHARSET_NAME}.
459     *
460     * @param exchange  the exchange
461     * @param useDefault should we fallback and use JVM default charset if no property existed?
462     * @return the charset, or <tt>null</tt> if no found
463     */
464    @Deprecated
465    public static String getCharsetName(Exchange exchange, boolean useDefault) {
466        return IOHelper.getCharsetName(exchange, useDefault);
467    }
468    
469    @Deprecated
470    public static String getCharsetName(Exchange exchange) {
471        return getCharsetName(exchange, true);
472    }
473
474    /**
475     * Encoding-aware input stream.
476     */
477    public static class EncodingInputStream extends InputStream {
478
479        private final File file;
480        private final BufferedReader reader;
481        private final Charset defaultStreamCharset;
482
483        private ByteBuffer bufferBytes;
484        private CharBuffer bufferedChars = CharBuffer.allocate(4096);
485
486        public EncodingInputStream(File file, String charset) throws IOException {
487            this.file = file;
488            reader = toReader(file, charset);
489            defaultStreamCharset = defaultCharset.get();
490        }
491
492        @Override
493        public int read() throws IOException {
494            if (bufferBytes == null || bufferBytes.remaining() <= 0) {
495                bufferedChars.clear();
496                int len = reader.read(bufferedChars);
497                bufferedChars.flip();
498                if (len == -1) {
499                    return -1;
500                }
501                bufferBytes = defaultStreamCharset.encode(bufferedChars);
502            }
503            return bufferBytes.get();
504        }
505
506        @Override
507        public void close() throws IOException {
508            reader.close();
509        }
510
511        @Override
512        public void reset() throws IOException {
513            reader.reset();
514        }
515
516        public InputStream toOriginalInputStream() throws FileNotFoundException {
517            return new FileInputStream(file);
518        }
519    }
520
521    /**
522     * Encoding-aware file reader. 
523     */
524    private static class EncodingFileReader extends InputStreamReader {
525
526        private final FileInputStream in;
527
528        /**
529         * @param in file to read
530         * @param charset character set to use
531         */
532        EncodingFileReader(FileInputStream in, String charset)
533            throws FileNotFoundException, UnsupportedEncodingException {
534            super(in, charset);
535            this.in = in;
536        }
537
538        @Override
539        public void close() throws IOException {
540            try {
541                super.close();
542            } finally {
543                in.close();
544            }
545        }
546    }
547    
548    /**
549     * Encoding-aware file writer. 
550     */
551    private static class EncodingFileWriter extends OutputStreamWriter {
552
553        private final FileOutputStream out;
554
555        /**
556         * @param out file to write
557         * @param charset character set to use
558         */
559        EncodingFileWriter(FileOutputStream out, String charset)
560            throws FileNotFoundException, UnsupportedEncodingException {
561            super(out, charset);
562            this.out = out;
563        }
564
565        @Override
566        public void close() throws IOException {
567            try {
568                super.close();
569            } finally {
570                out.close();
571            }
572        }
573    }
574    
575    /**
576     * This method will take off the quotes and double quotes of the charset
577     */
578    @Deprecated
579    public static String normalizeCharset(String charset) {
580        return IOHelper.normalizeCharset(charset);
581    }
582    
583    @Deprecated
584    public static void validateCharset(String charset) throws UnsupportedCharsetException {
585        IOHelper.validateCharset(charset);
586    }
587
588}