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.util;
018
019import java.io.BufferedInputStream;
020import java.io.BufferedOutputStream;
021import java.io.BufferedReader;
022import java.io.BufferedWriter;
023import java.io.ByteArrayInputStream;
024import java.io.Closeable;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.io.OutputStream;
030import java.io.Reader;
031import java.io.UnsupportedEncodingException;
032import java.io.Writer;
033import java.nio.channels.FileChannel;
034import java.nio.charset.Charset;
035import java.nio.charset.UnsupportedCharsetException;
036
037import org.apache.camel.Exchange;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * IO helper class.
043 *
044 * @version 
045 */
046public final class IOHelper {
047
048    public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
049
050    private static final Logger LOG = LoggerFactory.getLogger(IOHelper.class);
051    private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
052
053    // allows to turn on backwards compatible to turn off regarding the first read byte with value zero (0b0) as EOL.
054    // See more at CAMEL-11672
055    private static final boolean ZERO_BYTE_EOL_ENABLED =
056        "true".equalsIgnoreCase(System.getProperty("camel.zeroByteEOLEnabled", "true"));
057
058    private IOHelper() {
059        // Utility Class
060    }
061    
062    /**
063     * Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings.
064     */
065    public static String newStringFromBytes(byte[] bytes) {
066        try {
067            return new String(bytes, UTF8_CHARSET.name());
068        } catch (UnsupportedEncodingException e) {
069            throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
070        }
071    }
072
073    /**
074     * Use this function instead of new String(byte[], int, int) 
075     * to avoid surprises from non-standard default encodings.
076     */
077    public static String newStringFromBytes(byte[] bytes, int start, int length) {
078        try {
079            return new String(bytes, start, length, UTF8_CHARSET.name());
080        } catch (UnsupportedEncodingException e) {
081            throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e);
082        }
083    }
084
085    /**
086     * Wraps the passed <code>in</code> into a {@link BufferedInputStream}
087     * object and returns that. If the passed <code>in</code> is already an
088     * instance of {@link BufferedInputStream} returns the same passed
089     * <code>in</code> reference as is (avoiding double wrapping).
090     * 
091     * @param in the wrapee to be used for the buffering support
092     * @return the passed <code>in</code> decorated through a
093     *         {@link BufferedInputStream} object as wrapper
094     */
095    public static BufferedInputStream buffered(InputStream in) {
096        ObjectHelper.notNull(in, "in");
097        return (in instanceof BufferedInputStream) ? (BufferedInputStream)in : new BufferedInputStream(in);
098    }
099
100    /**
101     * Wraps the passed <code>out</code> into a {@link BufferedOutputStream}
102     * object and returns that. If the passed <code>out</code> is already an
103     * instance of {@link BufferedOutputStream} returns the same passed
104     * <code>out</code> reference as is (avoiding double wrapping).
105     * 
106     * @param out the wrapee to be used for the buffering support
107     * @return the passed <code>out</code> decorated through a
108     *         {@link BufferedOutputStream} object as wrapper
109     */
110    public static BufferedOutputStream buffered(OutputStream out) {
111        ObjectHelper.notNull(out, "out");
112        return (out instanceof BufferedOutputStream) ? (BufferedOutputStream)out : new BufferedOutputStream(out);
113    }
114
115    /**
116     * Wraps the passed <code>reader</code> into a {@link BufferedReader} object
117     * and returns that. If the passed <code>reader</code> is already an
118     * instance of {@link BufferedReader} returns the same passed
119     * <code>reader</code> reference as is (avoiding double wrapping).
120     * 
121     * @param reader the wrapee to be used for the buffering support
122     * @return the passed <code>reader</code> decorated through a
123     *         {@link BufferedReader} object as wrapper
124     */
125    public static BufferedReader buffered(Reader reader) {
126        ObjectHelper.notNull(reader, "reader");
127        return (reader instanceof BufferedReader) ? (BufferedReader)reader : new BufferedReader(reader);
128    }
129
130    /**
131     * Wraps the passed <code>writer</code> into a {@link BufferedWriter} object
132     * and returns that. If the passed <code>writer</code> is already an
133     * instance of {@link BufferedWriter} returns the same passed
134     * <code>writer</code> reference as is (avoiding double wrapping).
135     * 
136     * @param writer the wrapee to be used for the buffering support
137     * @return the passed <code>writer</code> decorated through a
138     *         {@link BufferedWriter} object as wrapper
139     */
140    public static BufferedWriter buffered(Writer writer) {
141        ObjectHelper.notNull(writer, "writer");
142        return (writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer);
143    }
144
145    /**
146     * A factory method which creates an {@link IOException} from the given
147     * exception and message
148     *
149     * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
150     */
151    @Deprecated
152    public static IOException createIOException(Throwable cause) {
153        return createIOException(cause.getMessage(), cause);
154    }
155
156    /**
157     * A factory method which creates an {@link IOException} from the given
158     * exception and message
159     *
160     * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0
161     */
162    @Deprecated
163    public static IOException createIOException(String message, Throwable cause) {
164        IOException answer = new IOException(message);
165        answer.initCause(cause);
166        return answer;
167    }
168
169    public static int copy(InputStream input, OutputStream output) throws IOException {
170        return copy(input, output, DEFAULT_BUFFER_SIZE);
171    }
172
173    public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
174        return copy(input, output, bufferSize, false);
175    }
176
177    public static int copy(final InputStream input, final OutputStream output, int bufferSize, boolean flushOnEachWrite) throws IOException {
178        if (input instanceof ByteArrayInputStream) {
179            // optimized for byte array as we only need the max size it can be
180            input.mark(0);
181            input.reset();
182            bufferSize = input.available();
183        } else {
184            int avail = input.available();
185            if (avail > bufferSize) {
186                bufferSize = avail;
187            }
188        }
189
190        if (bufferSize > 262144) {
191            // upper cap to avoid buffers too big
192            bufferSize = 262144;
193        }
194
195        if (LOG.isTraceEnabled()) {
196            LOG.trace("Copying InputStream: {} -> OutputStream: {} with buffer: {} and flush on each write {}",
197                    new Object[]{input, output, bufferSize, flushOnEachWrite});
198        }
199
200        int total = 0;
201        final byte[] buffer = new byte[bufferSize];
202        int n = input.read(buffer);
203
204        boolean hasData;
205        if (ZERO_BYTE_EOL_ENABLED) {
206            // workaround issue on some application servers which can return 0 (instead of -1)
207            // as first byte to indicate end of stream (CAMEL-11672)
208            hasData = n > 0;
209        } else {
210            hasData = n > -1;
211        }
212        if (hasData) {
213            while (-1 != n) {
214                output.write(buffer, 0, n);
215                if (flushOnEachWrite) {
216                    output.flush();
217                }
218                total += n;
219                n = input.read(buffer);
220            }
221        }
222        if (!flushOnEachWrite) {
223            // flush at end, if we didn't do it during the writing
224            output.flush();
225        }
226        return total;
227    }
228    
229    public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException {
230        copyAndCloseInput(input, output, DEFAULT_BUFFER_SIZE);
231    }
232    
233    public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException {
234        copy(input, output, bufferSize);
235        close(input, null, LOG);
236    }
237
238    public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException {
239        final char[] buffer = new char[bufferSize];
240        int n = input.read(buffer);
241        int total = 0;
242        while (-1 != n) {
243            output.write(buffer, 0, n);
244            total += n;
245            n = input.read(buffer);
246        }
247        output.flush();
248        return total;
249    }
250
251    /**
252     * Forces any updates to this channel's file to be written to the storage device that contains it.
253     *
254     * @param channel the file channel
255     * @param name the name of the resource
256     * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
257     */
258    public static void force(FileChannel channel, String name, Logger log) {
259        try {
260            if (channel != null) {
261                channel.force(true);
262            }
263        } catch (Exception e) {
264            if (log == null) {
265                // then fallback to use the own Logger
266                log = LOG;
267            }
268            if (name != null) {
269                log.warn("Cannot force FileChannel: " + name + ". Reason: " + e.getMessage(), e);
270            } else {
271                log.warn("Cannot force FileChannel. Reason: {}", e.getMessage(), e);
272            }
273        }
274    }
275
276    /**
277     * Forces any updates to a FileOutputStream be written to the storage device that contains it.
278     *
279     * @param os the file output stream
280     * @param name the name of the resource
281     * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
282     */
283    public static void force(FileOutputStream os, String name, Logger log) {
284        try {
285            if (os != null) {
286                os.getFD().sync();
287            }
288        } catch (Exception e) {
289            if (log == null) {
290                // then fallback to use the own Logger
291                log = LOG;
292            }
293            if (name != null) {
294                log.warn("Cannot sync FileDescriptor: " + name + ". Reason: " + e.getMessage(), e);
295            } else {
296                log.warn("Cannot sync FileDescriptor. Reason: {}", e.getMessage(), e);
297            }
298        }
299    }
300
301    /**
302     * Closes the given writer, logging any closing exceptions to the given log.
303     * An associated FileOutputStream can optionally be forced to disk.
304     *
305     * @param writer the writer to close
306     * @param os an underlying FileOutputStream that will to be forced to disk according to the force parameter
307     * @param name the name of the resource
308     * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
309     * @param force forces the FileOutputStream to disk
310     */
311    public static void close(Writer writer, FileOutputStream os, String name, Logger log, boolean force) {
312        if (writer != null && force) {
313            // flush the writer prior to syncing the FD
314            try {
315                writer.flush();
316            } catch (Exception e) {
317                if (log == null) {
318                    // then fallback to use the own Logger
319                    log = LOG;
320                }
321                if (name != null) {
322                    log.warn("Cannot flush Writer: " + name + ". Reason: " + e.getMessage(), e);
323                } else {
324                    log.warn("Cannot flush Writer. Reason: {}", e.getMessage(), e);
325                }
326            }
327            force(os, name, log);
328        }
329        close(writer, name, log);
330    }
331
332    /**
333     * Closes the given resource if it is available, logging any closing exceptions to the given log.
334     *
335     * @param closeable the object to close
336     * @param name the name of the resource
337     * @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
338     */
339    public static void close(Closeable closeable, String name, Logger log) {
340        if (closeable != null) {
341            try {
342                closeable.close();
343            } catch (IOException e) {
344                if (log == null) {
345                    // then fallback to use the own Logger
346                    log = LOG;
347                }
348                if (name != null) {
349                    log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e);
350                } else {
351                    log.warn("Cannot close. Reason: {}", e.getMessage(), e);
352                }
353            }
354        }
355    }
356    
357    /**
358     * Closes the given resource if it is available and don't catch the exception
359     *
360     * @param closeable the object to close
361     * @throws IOException
362      */
363    public static void closeWithException(Closeable closeable) throws IOException {
364        if (closeable != null) {
365            try {
366                closeable.close();
367            } catch (IOException e) {
368                // don't catch the exception here
369                throw e;
370            }
371        }
372    }
373
374    /**
375     * Closes the given channel if it is available, logging any closing exceptions to the given log.
376     * The file's channel can optionally be forced to disk.
377     *
378     * @param channel the file channel
379     * @param name the name of the resource
380     * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
381     * @param force forces the file channel to disk
382     */
383    public static void close(FileChannel channel, String name, Logger log, boolean force) {
384        if (force) {
385            force(channel, name, log);
386        }
387        close(channel, name, log);
388    }
389
390    /**
391     * Closes the given resource if it is available.
392     *
393     * @param closeable the object to close
394     * @param name the name of the resource
395     */
396    public static void close(Closeable closeable, String name) {
397        close(closeable, name, LOG);
398    }
399
400    /**
401     * Closes the given resource if it is available.
402     *
403     * @param closeable the object to close
404     */
405    public static void close(Closeable closeable) {
406        close(closeable, null, LOG);
407    }
408
409    /**
410     * Closes the given resources if they are available.
411     * 
412     * @param closeables the objects to close
413     */
414    public static void close(Closeable... closeables) {
415        for (Closeable closeable : closeables) {
416            close(closeable);
417        }
418    }
419
420    public static void closeIterator(Object it) throws IOException {
421        if (it instanceof java.util.Scanner) {
422            // special for Scanner which implement the Closeable since JDK7
423            java.util.Scanner scanner = (java.util.Scanner) it;
424            scanner.close();
425            IOException ioException = scanner.ioException();
426            if (ioException != null) {
427                throw ioException;
428            }
429        } else if (it instanceof Scanner) {
430            // special for Scanner which implement the Closeable since JDK7
431            Scanner scanner = (Scanner) it;
432            scanner.close();
433            IOException ioException = scanner.ioException();
434            if (ioException != null) {
435                throw ioException;
436            }
437
438        } else if (it instanceof Closeable) {
439            IOHelper.closeWithException((Closeable) it);
440        }
441    }
442
443    public static void validateCharset(String charset) throws UnsupportedCharsetException {
444        if (charset != null) {
445            if (Charset.isSupported(charset)) {
446                Charset.forName(charset);
447                return;
448            }
449        }
450        throw new UnsupportedCharsetException(charset);
451    }
452
453    /**
454     * This method will take off the quotes and double quotes of the charset
455     */
456    public static String normalizeCharset(String charset) {
457        if (charset != null) {
458            String answer = charset.trim();
459            if (answer.startsWith("'") || answer.startsWith("\"")) {
460                answer = answer.substring(1);
461            }
462            if (answer.endsWith("'") || answer.endsWith("\"")) {
463                answer = answer.substring(0, answer.length() - 1);
464            }
465            return answer.trim();
466        } else {
467            return null;
468        }
469    }
470
471    /**
472     * @see #getCharsetName(org.apache.camel.Exchange, boolean)
473     */
474    public static String getCharsetName(Exchange exchange) {
475        return getCharsetName(exchange, true);
476    }
477
478    /**
479     * Gets the charset name if set as header or property {@link Exchange#CHARSET_NAME}.
480     * <b>Notice:</b> The lookup from the header has priority over the property.
481     *
482     * @param exchange  the exchange
483     * @param useDefault should we fallback and use JVM default charset if no property existed?
484     * @return the charset, or <tt>null</tt> if no found
485     */
486    public static String getCharsetName(Exchange exchange, boolean useDefault) {
487        if (exchange != null) {
488            // header takes precedence
489            String charsetName = exchange.getIn().getHeader(Exchange.CHARSET_NAME, String.class);
490            if (charsetName == null) {
491                charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
492            }
493            if (charsetName != null) {
494                return IOHelper.normalizeCharset(charsetName);
495            }
496        }
497        if (useDefault) {
498            return getDefaultCharsetName();
499        } else {
500            return null;
501        }
502    }
503    
504    private static String getDefaultCharsetName() {
505        return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8");
506    }
507
508    /**
509     * Loads the entire stream into memory as a String and returns it.
510     * <p/>
511     * <b>Notice:</b> This implementation appends a <tt>\n</tt> as line
512     * terminator at the of the text.
513     * <p/>
514     * Warning, don't use for crazy big streams :)
515     */
516    public static String loadText(InputStream in) throws IOException {
517        StringBuilder builder = new StringBuilder();
518        InputStreamReader isr = new InputStreamReader(in);
519        try {
520            BufferedReader reader = buffered(isr);
521            while (true) {
522                String line = reader.readLine();
523                if (line != null) {
524                    builder.append(line);
525                    builder.append("\n");
526                } else {
527                    break;
528                }
529            }
530            return builder.toString();
531        } finally {
532            close(isr, in);
533        }
534    }
535    
536    /**
537     * Get the charset name from the content type string
538     * @param contentType
539     * @return the charset name, or <tt>UTF-8</tt> if no found
540     */
541    public static String getCharsetNameFromContentType(String contentType) {
542        String[] values = contentType.split(";"); 
543        String charset = "";
544
545        for (String value : values) {
546            value = value.trim();
547            if (value.toLowerCase().startsWith("charset=")) {
548                // Take the charset name
549                charset = value.substring(8);
550            }
551        }
552        if ("".equals(charset)) {
553            charset = "UTF-8"; 
554        }
555        return IOHelper.normalizeCharset(charset);
556
557    }
558}