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 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 validateCharset(String charset) throws UnsupportedCharsetException { 421 if (charset != null) { 422 if (Charset.isSupported(charset)) { 423 Charset.forName(charset); 424 return; 425 } 426 } 427 throw new UnsupportedCharsetException(charset); 428 } 429 430 /** 431 * This method will take off the quotes and double quotes of the charset 432 */ 433 public static String normalizeCharset(String charset) { 434 if (charset != null) { 435 String answer = charset.trim(); 436 if (answer.startsWith("'") || answer.startsWith("\"")) { 437 answer = answer.substring(1); 438 } 439 if (answer.endsWith("'") || answer.endsWith("\"")) { 440 answer = answer.substring(0, answer.length() - 1); 441 } 442 return answer.trim(); 443 } else { 444 return null; 445 } 446 } 447 448 /** 449 * @see #getCharsetName(org.apache.camel.Exchange, boolean) 450 */ 451 public static String getCharsetName(Exchange exchange) { 452 return getCharsetName(exchange, true); 453 } 454 455 /** 456 * Gets the charset name if set as header or property {@link Exchange#CHARSET_NAME}. 457 * <b>Notice:</b> The lookup from the header has priority over the property. 458 * 459 * @param exchange the exchange 460 * @param useDefault should we fallback and use JVM default charset if no property existed? 461 * @return the charset, or <tt>null</tt> if no found 462 */ 463 public static String getCharsetName(Exchange exchange, boolean useDefault) { 464 if (exchange != null) { 465 // header takes precedence 466 String charsetName = exchange.getIn().getHeader(Exchange.CHARSET_NAME, String.class); 467 if (charsetName == null) { 468 charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class); 469 } 470 if (charsetName != null) { 471 return IOHelper.normalizeCharset(charsetName); 472 } 473 } 474 if (useDefault) { 475 return getDefaultCharsetName(); 476 } else { 477 return null; 478 } 479 } 480 481 private static String getDefaultCharsetName() { 482 return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8"); 483 } 484 485 /** 486 * Loads the entire stream into memory as a String and returns it. 487 * <p/> 488 * <b>Notice:</b> This implementation appends a <tt>\n</tt> as line 489 * terminator at the of the text. 490 * <p/> 491 * Warning, don't use for crazy big streams :) 492 */ 493 public static String loadText(InputStream in) throws IOException { 494 StringBuilder builder = new StringBuilder(); 495 InputStreamReader isr = new InputStreamReader(in); 496 try { 497 BufferedReader reader = buffered(isr); 498 while (true) { 499 String line = reader.readLine(); 500 if (line != null) { 501 builder.append(line); 502 builder.append("\n"); 503 } else { 504 break; 505 } 506 } 507 return builder.toString(); 508 } finally { 509 close(isr, in); 510 } 511 } 512 513 /** 514 * Get the charset name from the content type string 515 * @param contentType 516 * @return the charset name, or <tt>UTF-8</tt> if no found 517 */ 518 public static String getCharsetNameFromContentType(String contentType) { 519 String[] values = contentType.split(";"); 520 String charset = ""; 521 522 for (String value : values) { 523 value = value.trim(); 524 if (value.toLowerCase().startsWith("charset=")) { 525 // Take the charset name 526 charset = value.substring(8); 527 } 528 } 529 if ("".equals(charset)) { 530 charset = "UTF-8"; 531 } 532 return IOHelper.normalizeCharset(charset); 533 534 } 535}