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