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